diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 00000000..b656be27
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,17 @@
+# EditorConfig is awesome: http://EditorConfig.org
+root = true
+
+[*]
+charset = utf-8
+indent_size = 2
+indent_style = space
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[{.gitignore,.npmignore}]
+end_of_line =
+trim_trailing_whitespace = false
+
+[*.md]
+trim_trailing_whitespace = false
diff --git a/.eslintrc.js b/.eslintrc.js
new file mode 100644
index 00000000..eccc6811
--- /dev/null
+++ b/.eslintrc.js
@@ -0,0 +1,334 @@
+module.exports = {
+ 'env': {
+ 'browser': true,
+ 'commonjs': true,
+ 'es6': true
+ },
+ 'extends': 'eslint:recommended',
+ 'globals': {
+ 'Atomics': 'readonly',
+ 'SharedArrayBuffer': 'readonly'
+ },
+ 'parserOptions': {
+ 'ecmaVersion': 2018
+ },
+ 'rules': {
+ 'accessor-pairs': 'error',
+ 'array-bracket-newline': 'error',
+ 'array-bracket-spacing': [
+ 'error',
+ 'never'
+ ],
+ 'array-callback-return': 'off',
+ 'array-element-newline': 'off',
+ 'arrow-body-style': 'off',
+ 'arrow-parens': 'off',
+ 'arrow-spacing': [
+ 'error',
+ {
+ 'after': true,
+ 'before': true
+ }
+ ],
+ 'block-scoped-var': 'error',
+ 'block-spacing': 'error',
+ 'brace-style': [
+ 'error',
+ '1tbs'
+ ],
+ 'callback-return': 'error',
+ 'camelcase': 'error',
+ 'capitalized-comments': 'off',
+ 'class-methods-use-this': 'off',
+ 'comma-dangle': 'error',
+ 'comma-spacing': [
+ 'error',
+ {
+ 'after': true,
+ 'before': false
+ }
+ ],
+ 'comma-style': [
+ 'error',
+ 'last'
+ ],
+ 'complexity': 'error',
+ 'computed-property-spacing': [
+ 'error',
+ 'never'
+ ],
+ 'consistent-return': 'off',
+ 'consistent-this': 'error',
+ 'curly': 'off',
+ 'default-case': 'error',
+ 'dot-location': [
+ 'error',
+ 'property'
+ ],
+ 'dot-notation': [
+ 'error',
+ {
+ 'allowKeywords': true
+ }
+ ],
+ 'eol-last': 'error',
+ 'eqeqeq': 'error',
+ 'func-call-spacing': 'error',
+ 'func-name-matching': 'error',
+ 'func-names': 'off',
+ 'func-style': 'off',
+ 'function-paren-newline': 'error',
+ 'generator-star-spacing': 'error',
+ 'global-require': 'off',
+ 'guard-for-in': 'off',
+ 'handle-callback-err': 'error',
+ 'id-blacklist': 'error',
+ 'id-length': 'off',
+ 'id-match': 'error',
+ 'implicit-arrow-linebreak': [
+ 'error',
+ 'beside'
+ ],
+ 'indent': 'off',
+ 'indent-legacy': 'off',
+ 'init-declarations': 'off',
+ 'jsx-quotes': 'error',
+ 'key-spacing': 'error',
+ 'keyword-spacing': [
+ 'error',
+ {
+ 'after': true,
+ 'before': true
+ }
+ ],
+ 'line-comment-position': 'off',
+ 'linebreak-style': [
+ 'error',
+ 'unix'
+ ],
+ 'lines-around-comment': 'off',
+ 'lines-around-directive': 'error',
+ 'lines-between-class-members': [
+ 'error',
+ 'always'
+ ],
+ 'max-classes-per-file': 'error',
+ 'max-depth': 'error',
+ 'max-len': 'error',
+ 'max-lines': 'off',
+ 'max-lines-per-function': 'error',
+ 'max-nested-callbacks': 'error',
+ 'max-params': 'error',
+ 'max-statements': 'off',
+ 'max-statements-per-line': 'error',
+ 'multiline-comment-style': [
+ 'error',
+ 'separate-lines'
+ ],
+ 'multiline-ternary': 'off',
+ 'new-cap': 'error',
+ 'new-parens': 'error',
+ 'newline-after-var': 'off',
+ 'newline-before-return': 'off',
+ 'newline-per-chained-call': 'off',
+ 'no-alert': 'error',
+ 'no-array-constructor': 'error',
+ 'no-async-promise-executor': 'error',
+ 'no-await-in-loop': 'error',
+ 'no-bitwise': 'error',
+ 'no-buffer-constructor': 'error',
+ 'no-caller': 'error',
+ 'no-catch-shadow': 'error',
+ 'no-confusing-arrow': 'error',
+ 'no-continue': 'off',
+ 'no-div-regex': 'error',
+ 'no-duplicate-imports': 'error',
+ 'no-else-return': 'off',
+ 'no-empty-function': 'error',
+ 'no-eq-null': 'error',
+ 'no-eval': 'error',
+ 'no-extend-native': 'error',
+ 'no-extra-bind': 'error',
+ 'no-extra-label': 'error',
+ 'no-extra-parens': 'error',
+ 'no-floating-decimal': 'error',
+ 'no-implicit-coercion': 'error',
+ 'no-implicit-globals': 'error',
+ 'no-implied-eval': 'error',
+ 'no-inline-comments': 'off',
+ 'no-inner-declarations': [
+ 'error',
+ 'functions'
+ ],
+ 'no-invalid-this': 'error',
+ 'no-iterator': 'error',
+ 'no-label-var': 'error',
+ 'no-labels': 'error',
+ 'no-lone-blocks': 'error',
+ 'no-lonely-if': 'off',
+ 'no-loop-func': 'error',
+ 'no-magic-numbers': 'off',
+ 'no-misleading-character-class': 'error',
+ 'no-mixed-operators': 'off',
+ 'no-mixed-requires': 'error',
+ 'no-multi-assign': 'off',
+ 'no-multi-spaces': 'error',
+ 'no-multi-str': 'error',
+ 'no-multiple-empty-lines': 'error',
+ 'no-native-reassign': 'error',
+ 'no-negated-condition': 'off',
+ 'no-negated-in-lhs': 'error',
+ 'no-nested-ternary': 'error',
+ 'no-new': 'error',
+ 'no-new-func': 'error',
+ 'no-new-object': 'error',
+ 'no-new-require': 'error',
+ 'no-new-wrappers': 'error',
+ 'no-octal-escape': 'error',
+ 'no-param-reassign': 'off',
+ 'no-path-concat': 'off',
+ 'no-plusplus': 'off',
+ 'no-process-env': 'off',
+ 'no-process-exit': 'off',
+ 'no-proto': 'error',
+ 'no-prototype-builtins': 'off',
+ 'no-restricted-globals': 'error',
+ 'no-restricted-imports': 'error',
+ 'no-restricted-modules': 'error',
+ 'no-restricted-properties': 'error',
+ 'no-restricted-syntax': 'error',
+ 'no-return-assign': 'error',
+ 'no-return-await': 'error',
+ 'no-script-url': 'error',
+ 'no-self-compare': 'error',
+ 'no-sequences': 'error',
+ 'no-shadow': 'off',
+ 'no-shadow-restricted-names': 'error',
+ 'no-spaced-func': 'error',
+ 'no-sync': 'off',
+ 'no-tabs': 'error',
+ 'no-template-curly-in-string': 'error',
+ 'no-ternary': 'off',
+ 'no-throw-literal': 'error',
+ 'no-trailing-spaces': 'error',
+ 'no-undef-init': 'error',
+ 'no-undefined': 'off',
+ 'no-underscore-dangle': 'off',
+ 'no-unmodified-loop-condition': 'error',
+ 'no-unneeded-ternary': 'error',
+ 'no-unused-expressions': 'error',
+ 'no-use-before-define': 'off',
+ 'no-useless-call': 'error',
+ 'no-useless-catch': 'error',
+ 'no-useless-computed-key': 'error',
+ 'no-useless-concat': 'error',
+ 'no-useless-constructor': 'error',
+ 'no-useless-rename': 'error',
+ 'no-useless-return': 'error',
+ 'no-var': 'off',
+ 'no-void': 'error',
+ 'no-warning-comments': 'off',
+ 'no-whitespace-before-property': 'error',
+ 'no-with': 'error',
+ 'nonblock-statement-body-position': [
+ 'error',
+ 'any'
+ ],
+ 'object-curly-newline': 'error',
+ 'object-curly-spacing': [
+ 'error',
+ 'never'
+ ],
+ 'object-shorthand': 'off',
+ 'one-var': 'off',
+ 'one-var-declaration-per-line': 'error',
+ 'operator-assignment': [
+ 'error',
+ 'always'
+ ],
+ 'operator-linebreak': [
+ 'error',
+ 'after'
+ ],
+ 'padded-blocks': 'off',
+ 'padding-line-between-statements': 'error',
+ 'prefer-arrow-callback': 'off',
+ 'prefer-const': 'off',
+ 'prefer-destructuring': 'off',
+ 'prefer-named-capture-group': 'error',
+ 'prefer-numeric-literals': 'error',
+ 'prefer-object-spread': 'error',
+ 'prefer-promise-reject-errors': [
+ 'error',
+ {
+ 'allowEmptyReject': true
+ }
+ ],
+ 'prefer-reflect': 'off',
+ 'prefer-rest-params': 'error',
+ 'prefer-spread': 'off',
+ 'prefer-template': 'off',
+ 'quote-props': 'off',
+ 'quotes': [
+ 'error',
+ 'single'
+ ],
+ 'radix': [
+ 'error',
+ 'as-needed'
+ ],
+ 'require-atomic-updates': 'error',
+ 'require-await': 'error',
+ 'require-jsdoc': 'off',
+ 'require-unicode-regexp': 'off',
+ 'rest-spread-spacing': 'error',
+ 'semi': 'error',
+ 'semi-spacing': [
+ 'error',
+ {
+ 'after': true,
+ 'before': false
+ }
+ ],
+ 'semi-style': [
+ 'error',
+ 'last'
+ ],
+ 'sort-imports': 'error',
+ 'sort-keys': 'off',
+ 'sort-vars': 'off',
+ 'space-before-blocks': 'error',
+ 'space-before-function-paren': 'off',
+ 'space-in-parens': [
+ 'error',
+ 'never'
+ ],
+ 'space-infix-ops': 'off',
+ 'space-unary-ops': 'error',
+ 'spaced-comment': [
+ 'error',
+ 'always'
+ ],
+ 'strict': 'error',
+ 'switch-colon-spacing': 'error',
+ 'symbol-description': 'error',
+ 'template-curly-spacing': [
+ 'error',
+ 'never'
+ ],
+ 'template-tag-spacing': 'error',
+ 'unicode-bom': [
+ 'error',
+ 'never'
+ ],
+ 'valid-jsdoc': 'off',
+ 'vars-on-top': 'off',
+ 'wrap-iife': 'error',
+ 'wrap-regex': 'error',
+ 'yield-star-spacing': 'error',
+ 'yoda': [
+ 'error',
+ 'never'
+ ]
+ }
+};
diff --git a/.gitignore b/.gitignore
index 763529fd..c7d5d95c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,7 @@
+lib
+lib-cov
node_modules
+test/test-coverage.html
.idea
*.iml
diff --git a/.jscs.json b/.jscs.json
deleted file mode 100644
index 2b0a0761..00000000
--- a/.jscs.json
+++ /dev/null
@@ -1,76 +0,0 @@
-{
- "excludeFiles": [
- "node_modules/**"
- ],
- "requireCurlyBraces": [
- "else",
- "while",
- "do"
- ],
- "requireSpaceAfterKeywords": [
- "if",
- "else",
- "for",
- "while",
- "do",
- "switch",
- "return",
- "catch"
- ],
- "requireSpacesInFunctionExpression": { "beforeOpeningCurlyBrace": true },
- "disallowSpacesInFunctionExpression": { "beforeOpeningRoundBrace": true },
- "disallowMultipleVarDecl": true,
- "disallowLeftStickedOperators": [
- "?",
- "+",
- "-",
- "/",
- "*",
- "=",
- "==",
- "===",
- "!=",
- "!==",
- ">",
- ">=",
- "<",
- "<="
- ],
- "disallowRightStickedOperators": [
- "?",
- "+",
- "/",
- "*",
- ":",
- ",",
- "=",
- "==",
- "===",
- "!=",
- "!==",
- ">",
- ">=",
- "<",
- "<="
- ],
- "requireRightStickedOperators": ["!"],
- "requireLeftStickedOperators": [","],
- "disallowImplicitTypeConversion": [
- "numeric",
- "boolean",
- "binary",
- "string"
- ],
- "disallowKeywords": ["with"],
- "disallowKeywordsOnNewLine": ["else", "catch"],
- "requireLineFeedAtFileEnd": true,
- "validateJSDoc": {
- "checkParamNames": true,
- "checkRedundantParams": true,
- "requireParamTypes": true
- },
- "requireSpacesInsideObjectBrackets": "all",
- "disallowSpacesInsideArrayBrackets": true,
- "disallowSpaceAfterObjectKeys": true,
- "disallowQuotedKeysInObjects": true
-}
diff --git a/.jshint-groups.js b/.jshint-groups.js
deleted file mode 100644
index 70b48684..00000000
--- a/.jshint-groups.js
+++ /dev/null
@@ -1,35 +0,0 @@
-module.exports = {
- options: {
- eqeqeq: true,
- evil: true,
- expr: true,
- forin: true,
- immed: true,
- indent: 4,
- latedef: true,
- maxdepth: 5,
- maxlen: 120,
- maxparams: 4,
- newcap: true,
- noarg: true,
- noempty: true,
- nonew: true,
- quotmark: 'single',
- trailing: true,
- undef: true,
- unused: true
- },
- groups: {
- js: {
- options: { node: true },
- includes: ['lib/**/*.js']
- },
- test: {
- options: {
- node: true,
- predef: ['describe', 'beforeEach', 'it']
- },
- includes: ['test/*.js']
- }
- }
-};
diff --git a/.jshintrc b/.jshintrc
new file mode 100644
index 00000000..4b3b2959
--- /dev/null
+++ b/.jshintrc
@@ -0,0 +1,16 @@
+{
+ "eqeqeq": true,
+ "esnext": true,
+ "freeze": true,
+ "globals": {
+ "afterEach": false,
+ "beforeEach": false,
+ "describe": false,
+ "it": false
+ },
+ "node": true,
+ "strict": true,
+ "undef": true,
+ "unused": true,
+ "-W084": true
+}
diff --git a/.travis.yml b/.travis.yml
index b2118ad4..432dbd02 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,10 +1,6 @@
language: node_js
-
node_js:
- - "0.8"
- - "0.10"
- - "0.11"
-
-matrix:
- allow_failures:
- - node_js: "0.11"
+ - '11'
+ - '10'
+ - '8'
+ - '6'
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 58c8cb58..f6ec7421 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,176 @@
# Changelog
+## 4.3.0 - 2019-03-24
+
+- Updated GPE to v4.2.4
+- Updated list of supported Node version to reflect https://nodejs.org/en/about/releases/
+- Fixed #551
+- [cli] Fixed parsing of tty-mode option as boolean
+
+## 4.2.0 - 2017-06-12
+
+- Fixed - align for flexbox properies (#507)
+- Updated - replace '\n' with os.EOL for terminal output (#524)
+- Updated - vow-fs dependency (#526)
+- Fixed - Add empty new line in the end of cli help text (#516)
+- Fixed - Endless loop with empty files (#506)
+- Added - Include all flexbox properties in config files (#509)
+
+## 4.1.0 - 2017-05-16
+
+- Added new `--tty-mode` option for cli.
+- Fixed an issue with calling gpe methods on non-existing nodes.
+
+## 4.0.1 - 2017-02-28
+
+- Added `babel-polyfill` to dependencies.
+
+## 4.0.0 - 2017-02-16
+
+- Added note about maintenance mode
+- Updated GPE to v3.4.7, which fixed a number of errors
+- Added `lines-between-rulesets` option
+- Added support for stdin in cli
+- For `sort-order` option, divided `$include` into `$extend`, `$include name` and `$include`
+
+## 3.1.7 - 2015-06-09
+
+- Do not remove units from values starting from zeroes, like `0.5em` (#394)
+
+## 3.1.6 - 2015-06-09
+- Hotfix for issue when `unitless-zero` crashed on percentages in Less (#389)
+
+## 3.1.5 - 2015-05-27
+- Fixed dependencies
+
+## 3.1.4 - 2015-05-24
+- Fixed `space-after-opening-brace` option for empty media queries (#387)
+
+## 3.1.3 - 2015-05-20
+- Do not add extra space before combinators which go first in selectors (#381)
+
+## 3.1.2 - 2015-05-20
+- Fixed processing of hashes (#379)
+
+## 3.1.1 - 2015-05-20
+- Fixed `space-between-declarations` vs. media queries (#378)
+
+## 3.1.0 - 2015-05-19
+**Thanks to
+[@1000ch](https://github.com/1000ch),
+[@alexeykuzmin](https://github.com/alexeykuzmin),
+[@jonrohan](https://github.com/jonrohan)**
+- Used new Gonzales PE API
+- Fixed nested properties (#319)
+- Added support for symlinked configs
+- Added support for configs with any extension
+
+## 3.0.4 - 2014-11-15
+**Thanks to
+[@gonzalocasas](https://github.com/gonzalocasas),
+[@tomByrer](https://github.com/tomByrer)**
+- Improved docs
+- Fixed Gonzales PE version
+
+## 3.0.3 - 2014-09-29
+**Thanks to
+[@kizu](https://github.com/kizu),
+[@necolas](https://github.com/necolas)**
+- Improved README
+- Added non-essential files to npmignore
+- Renamed `CONTRIBUTE.md` to `CONTRIBUTING.md`
+- Fixed unwanted removal of empty lines between nested rulesets (#317)
+
+## 3.0.2 - 2014-09-21
+- Fixed Gonzales PE version
+
+## 3.0.1 - 2014-08-18
+**Thanks to
+[@mishanga](https://github.com/mishanga),
+[@cust0dian](https://github.com/cust0dian)**
+- Added `khtml` to list of vendor prefixes
+- Updated docs
+
+## 3.0.0 - 2014-07-19
+**Thanks to
+[@cvrebert](https://github.com/cvrebert),
+[@filtercake](https://github.com/filtercake),
+[@lefoy](https://github.com/lefoy),
+[@L0stSoul](https://github.com/L0stSoul),
+[@kizu](https://github.com/kizu),
+[@schneyra](https://github.com/schneyra),
+[@thejameskyle](https://github.com/thejameskyle),
+[@vecmezoni](https://github.com/vecmezoni)**
+
+New:
+
+- Sass support
+- Leftovers (#160)
+- Use [CSScomb Core](https://github.com/csscomb/core)
+- Plugins API (with `.use()` method)
+- `sort-order-fallback` (alphabetical sort order)
+- `space-before-colon`
+- `space-after-colon`
+- `space-before-combinator`
+- `space-after-combinator`
+- `space-before-selector-delimiter`
+- `space-after-selector-delimiter`
+- `space-before-opening-brace`
+- `space-after-opening-brace`
+- `space-before-closing-brace`
+- `space-after-declaration`
+- `tab-size`
+
+Removed:
+
+- `colon-space`
+- `combinator-space`
+- `rule-indent`
+- `stick-brace`
+
+Changed:
+
+- Divided `process` and `detect` methods
+- Made `getConfig` and `detect` static methods
+- Removed "best guess" logic from options
+- Excluded `bower_components/**` from processing
+- Updated badges at README page
+- Update Gonzales PE to v3.0
+- Various fixes
+
+## 2.0.5 - 2014-06-09
+- Updated dependencies (#229)
+
+## 2.0.4 - 2014-01-16
+- Fixed relative path to config (#164)
+
+## 2.0.3 - 2014-01-16
+- Documentation moved to `doc` directory
+- Fixed incorrect left indent (#153)
+
+## 2.0.2 - 2014-01-09
+- Added test coverage (#138)
+- Added test helpers (#147)
+- Fixed config file recursive searching (#151)
+- Fixed `block-indent` detecting (#148)
+- Fixed `quote` value setter (#149)
+- Fixed detection integral tests (#150)
+
+## 2.0.1 - 2013-12-23
+- Fix for `remove-empty-rulesets` option (#133)
+
+## 2.0.0 - 2013-12-18
+**Great thanks for @tonyganch and @kizu!**
+- Use Gonzales PE to parse *.scss and *.less files
+- Support sorting properties in *.scss and *.less files
+- Codestyle detection
+- Option: always-semicolon (scss + less)
+- Option: quotes
+- Two more config examples now: `csscomb` and `zen`
+- Lots of refactoring and fixes
+- Lots of tests added
+- Node v0.8 no longer supported
+
## 1.0.0 - 2013-11-06
- Option: vendor-prefix-align
- Dependencies updated
diff --git a/CONTRIBUTE.md b/CONTRIBUTING.md
similarity index 51%
rename from CONTRIBUTE.md
rename to CONTRIBUTING.md
index 93ec3412..2f309ed1 100644
--- a/CONTRIBUTE.md
+++ b/CONTRIBUTING.md
@@ -1,49 +1,63 @@
# Contributing to CSSComb
-
+## Table of Contents
+
+- [Pull requests](#pull-requests)
+- [For maintainers](#for-maintainers)
+
## Pull requests
-1. [Fork](http://help.github.com/fork-a-repo/) the project, clone your fork, and configure the remotes:
+#### 1. [Fork](http://help.github.com/fork-a-repo/) the project, clone your fork, and configure the remotes:
+
+a. Clone your fork of the repo into the current directory
```bash
-# Clone your fork of the repo into the current directory
git clone https://github.com//csscomb.js
-# Navigate to the newly cloned directory
+```
+
+b. Navigate to the newly cloned directory
+```bash
cd csscomb.js
-# Assign the original repo to a remote called `upstream`
+```
+
+c. Assign the original repo to a remote called `upstream`
+```bash
git remote add upstream https://github.com/csscomb/csscomb.js
```
-2. If you cloned a while ago, get the latest changes from upstream:
+#### 2. If you cloned a while ago, get the latest changes from upstream:
+
```bash
git checkout dev
git pull upstream dev
```
-**IMPORTANT**: We are using `dev` branch for development, not `master`.
-3. Create a topic branch for your feature, change, or fix:
+> **IMPORTANT**: We are using `dev` branch for development, not `master`.
+
+#### 3. Create a topic branch for your feature, change, or fix:
+
```bash
git checkout -b
```
-4. Patches and features will not be accepted without tests.
- Run `npm test` to check that all tests pass after you've made changes.
+#### 4. Patches and features will not be accepted without tests.
+Run `npm test` to check that all tests pass after you've made changes.
+
+#### 5. Update the `README.md` or [docs](https://github.com/csscomb/csscomb.js/tree/master/doc) if there were corresponding changes or new options.
-5. Update the `README.md` if there were corresponding changes or new options.
+#### 6. Locally rebase the upstream development branch into your topic branch:
-6. Locally rebase the upstream development branch into your topic branch:
```bash
git pull --rebase upstream dev
```
-7. Push your topic branch up to your fork:
+#### 7. Push your topic branch up to your fork:
```bash
git push origin
```
-8. [Open a Pull Request](https://help.github.com/articles/using-pull-requests/) to a `dev` branch with a clear title and description.
+#### 8. [Open a Pull Request](https://help.github.com/articles/using-pull-requests/) to a `dev` branch with a clear title and description.
-
-## Maintainers
+## For maintainers
### Submitting changes
diff --git a/README.md b/README.md
index 9adcfc26..94da9d3d 100644
--- a/README.md
+++ b/README.md
@@ -1,469 +1,107 @@
-# CSSComb [](http://csscomb.com/)
-[](http://travis-ci.org/csscomb/csscomb.js)
+# CSScomb [](http://csscomb.com/)
+[](http://travis-ci.org/csscomb/csscomb.js)
+[](http://badge.fury.io/js/csscomb)
+[](https://david-dm.org/csscomb/csscomb.js)
+[](https://david-dm.org/csscomb/csscomb.js#info=devDependencies)
-CSSComb is a coding style formatter for CSS.
-You can easily write your own [configuration](#configuration) to make your style sheets beautiful and consistent.
+CSScomb is a coding style formatter for CSS.
+You can easily write your own [configuration](doc/configuration.md) to make
+your style sheets beautiful and consistent.
-The main feature is the [sorting properties](#sort-order) in specific order.
-It was inspired by the same-named [@miripiruni](https://github.com/miripiruni)'s [PHP-based tool](https://github.com/csscomb/csscomb).
-This is the new JavaScript version, based on powerful CSS parser [Gonzales](https://github.com/css/gonzales).
+The main feature is [sorting properties](doc/options.md#sort-order) in a specific order.
+It was inspired by [@miripiruni](https://github.com/miripiruni)'s
+[PHP-based tool](https://github.com/csscomb/csscomb) of the same name.
+This is the new JavaScript version, based on the powerful CSS parser
+[Gonzales PE](https://github.com/tonyganch/gonzales-pe).
-## Installation
+## 1. Install
-```bash
-npm install csscomb
-```
-
-To run `csscomb`, you can use the following command from the project root:
+Global installation (for use as a command-line tool):
```bash
-./node_modules/.bin/csscomb path[ path[...]]
-```
-
-```bash
-./node_modules/.bin/csscomb --help
-
- Usage: csscomb [options]
-
- Options:
-
- -h, --help output usage information
- -V, --version output the version number
- -c, --config [path] configuration file path
- -l, --lint in case some fixes needed returns an error
+npm install csscomb -g
```
-## Configuration
-
-`csscomb` is configured using [.csscomb.json](https://github.com/csscomb/csscomb.js/blob/master/.csscomb.json) file, located in the project root.
-
-Example configuration:
-```json
-{
- "exclude": ["node_modules/**"],
- "verbose": true,
-
- "always-semicolon": true,
- "block-indent": true,
- "colon-space": true,
- "color-case": "lower",
- "color-shorthand": true,
- "element-case": "lower",
- "eof-newline": true,
- "leading-zero": false,
- "remove-empty-rulesets": true,
- "rule-indent": true,
- "stick-brace": true,
- "strip-spaces": true,
- "unitless-zero": true,
- "vendor-prefix-align": true
-}
-```
-
-## Options
-
-### exclude
-
-Available values: `{String[]}`
-
-Array of [Ant path patterns](http://ant.apache.org/manual/dirtasks.html#patterns) to exclude.
+Local installation (for use as a command-line tool within current directory):
-Example: `{ "exclude": ["node_modules/**"] }` - exclude all files and directories under `node_modules` dir.
-
-### verbose
-
-Available value: `{Boolean}` `true`
-
-Config mode: `{ "verbose": true }`
```bash
-$ ./bin/csscomb ./test
-✓ test/integral.origin.css
- test/integral.expect.css
-
-2 files processed
-1 file fixed
-96 ms spent
-```
-
-CLI mode:
-```bash
-$ ./bin/csscomb ./test --verbose
-$ ./bin/csscomb ./test -v
-```
-
-### always-semicolon
-
-Available value: `{Boolean}` `true`
-
-Example: `{ "always-semicolon": true }`
-
-```css
-/* before */
-a { color: red }
-
-/* after */
-a { color: red; }
-```
-
-### block-indent
-
-**Note**: better to use with [rule-indent](#rule-indent)
-
-Available values:
- * `{Boolean}` `true` (means 4 spaces)
- * `{Number}` of spaces
- * `{String}` of whitespace characters (`/[ \t]+/`)
-
-Example: `{ "block-indent": 2 }`
-
-```css
-/* before */
- a { color: red }
- @media all { a { color: green } }
-
-/* after */
-a { color: red
-}
-@media all {
- a { color: green
- }
-}
-```
-
-### colon-space
-
-Available values:
- * `{Boolean}` `true` (means `after`) or `false` (no whitespace at all)
- * `{String}`: `before`, `after`, `both` or any combination of whitespaces
- and/or a colon (` `, `: `, `\t:\n\t` etc.)
- * `{Array}` with two `{String}` values: for setting left and right whitespace around a colon
-
-Example: `{ "colon-space": true }`
-
-```css
-/* before */
-a { color:red }
-
-/* after */
-a { color: red }
-```
-
-Example: `{ "colon-space": ":\n\t\t" }`
-
-```css
-/* before */
-a {
- color: red;
-}
-
-/* after */
-a {
- color:
- red;
-}
-```
-
-Example: `{ "colon-space": "" }`
-
-```css
-/* before */
-a { color: red }
-
-/* after */
-a { color:red }
-```
-
-Example: `{ "colon-space": ["\t", "\t"] }`
-
-```css
-/* before */
-a { color: red }
-
-/* after */
-a { color : red }
-```
-
-### color-case
-
-Available values: `{String}` `lower` or `upper`
-
-Example: `{ "color-case": "lower" }`
-
-```css
-/* before */
-a { color: #FFF }
-
-/* after */
-a { color: #fff }
-```
-
-### color-shorthand
-
-Available values: `{Boolean}` `true` or `false`
-
-Example: `{ "color-shorthand": true }`
-
-```css
-/* before */
-b { color: #ffcc00 }
-
-/* after */
-b { color: #fc0 }
-```
-
-### combinator-space
-
-Available values:
- * `{Boolean}`: `true` sets one space, `false` removes the spaces.
- * `{String}`: any combination of whitespaces.
- * `{Array}` with two `{String}` values: for setting left and right whitespace.
-
-Example: `{ "combinator-space": true }`
-
-```css
-/* before */
-a>b { color: red }
-
-/* after */
-a > b { color: red }
-```
-
-Example: `{ "combinator-space": "" }`
-
-```css
-/* before */
-a > b { color: red }
-
-/* after */
-a>b { color: red }
-```
-
-Example: `{ "combinator-space": [" ", "\n"] }`
-
-```css
-/* before */
-a>b { color: red }
-
-/* after */
-a >
-b { color: red }
-```
-
-### element-case
-
-Available values: `{String}` `lower` or `upper`
-
-Example: `{ "element-case": "upper" }`
-
-```css
-/* before */
-li > a { color: red }
-
-/* after */
-LI > A { color: red }
-```
-
-### eof-newline
-
-Available values: `{Boolean}` `true` or `false`
-
-Example: `{ "eof-newline": true }`
-
-`a { color: red }` → `a { color: red }\n`
-
-Example: `{ "eof-newline": false }`
-
-`a { color: red }\n` → `a { color: red }`
-
-### leading-zero
-
-Available values: `{Boolean}` `true` or `false`
-
-Example: `{ "leading-zero": false }`
-
-```css
-/* before */
-p { padding: 0.5em }
-
-/* after */
-p { padding: .5em }
-```
-
-### remove-empty-rulesets
-
-Available values: `{Boolean}` `true`
-
-Example: `{ "remove-empty-rulesets": true }` - remove rulesets that have no declarations or comments.
-
-`a { color: red; } p { /* hey */ } b { }` → `a { color: red; } p { /* hey */ } `
-
-### rule-indent
-
-**Note**: better to use with [block-indent](#block-indent)
-
-Available values:
- * `{Boolean}` `true` (means 4 spaces)
- * `{Number}` of spaces
- * `{String}` of whitespace characters (`/[ \t]+/`)
-
-Example: `{ "rule-indent": 2 }`
-
-```css
-/* before */
-a { color:red; margin:0 }
-
-/* after */
-a {
- color:red;
- margin:0 }
-```
-
-### sort-order
-
-**Note**: you can use an example of [.csscomb.json](https://github.com/csscomb/csscomb.js/blob/master/.csscomb.json) to set your own sort order
-
-Available values:
- * `{Array}` of rules
- * `{Array}` of arrays of rules for groups separation
-
-Example: `{ "sort-order": [ "margin", "padding" ] }`
-
-```css
-/* before */
-p {
- padding: 0;
- margin: 0;
-}
-
-/* after */
-p {
- margin: 0;
- padding: 0;
-}
+npm install csscomb
```
-Example: `{ "sort-order": [ [ "margin", "padding" ], [ "border", "background" ] ] }`
-
-```css
-/* before */
-p {
- background: none;
- border: 0;
- margin: 0;
- padding: 0;
-}
+To install as a project dependency (the package will appear in your dependencies):
-/* after */
-p {
- margin: 0;
- padding: 0;
-
- border: 0;
- background: none;
-}
+```bash
+npm install csscomb --save
```
-### stick-brace
-
-Available values:
- * `{Boolean}` `true` (means 1 space)
- * `{Number}` of spaces
- * `{String}` of whitespace characters (`/[ \t\n]+/`)
-
-Example: `{ "stick-brace": "\n" }`
+To install as a dev dependency (the package will appear in your devDependencies):
-```css
-/* before */
-a { color:red }
-
-/* after */
-a
-{ color:red }
+```bash
+npm install csscomb --save-dev
```
-### strip-spaces
-
-Available value: `{Boolean}` `true`
-
-Example: `{ "strip-spaces": true }`
-
-`a { color: red } \n \n \n` → `a { color: red }\n`
+## 2. [Configure](doc/configuration.md)
-`a { color: red }\t` → `a { color: red }`
+There are a number of ways to configure CSScomb:
-### unitless-zero
+- Use one of [predefined configs](config)
+- Put `.csscomb.json` file in the project root.
+- Set path to config's file
+- Use `*.css` file as a template
-Available value: `{Boolean}` `true`
+## 3. Use
-Example: `{ "unitless-zero": true }`
+### [Command Line](doc/usage-cli.md)
-```css
-/* before */
-img { border: 0px }
-
-/* after */
-img { border: 0 }
+```bash
+csscomb assets/css
```
-### vendor-prefix-align
-
-Available value: `{Boolean}` `true`
-
-Example: `{ "vendor-prefix-align": true }`
+### [Node.js module](doc/usage-node.md)
-```css
-/* before */
-a
-{
- -webkit-border-radius: 3px;
- -moz-border-radius: 3px;
- border-radius: 3px;
- background: -webkit-linear-gradient(top, #fff 0, #eee 100%);
- background: -moz-linear-gradient(top, #fff 0, #eee 100%);
- background: linear-gradient(to bottom, #fff 0, #eee 100%);
-}
-
-/* after */
-a
-{
- -webkit-border-radius: 3px;
- -moz-border-radius: 3px;
- border-radius: 3px;
- background: -webkit-linear-gradient(top, #fff 0, #eee 100%);
- background: -moz-linear-gradient(top, #fff 0, #eee 100%);
- background: linear-gradient(to bottom, #fff 0, #eee 100%);
-}
+```js
+var Comb = require('csscomb');
+var comb = new Comb('zen');
+comb.processPath('assets/css');
```
-## Tests
-
-Run `npm test` for tests.
+## 4. Contribute
-## Contributing
+This project is actively mantained. But anyone and everyone is welcome to contribute.
+Please take a moment to review the [guidelines for contributing](CONTRIBUTING.md).
-Anyone and everyone is welcome to contribute. Please take a moment to
-review the [guidelines for contributing](CONTRIBUTE.md).
+Also you can become a mantainer. To do that please ping
+[@tonyganch](https://github.com/tonyganch).
## Authors
-[@mishanga](https://github.com/mishanga)
+[@mishanga](https://github.com/mishanga),
+[@tonyganch](https://github.com/tonyganch)
Thanks for assistance and contributions:
[@miripiruni](https://github.com/miripiruni),
-[@puzankov](https://github.com/puzankov),
-[@L0stSoul](https://github.com/L0stSoul),
+[@anton-rudeshko](https://github.com/anton-rudeshko),
+[@cvrebert](https://github.com/cvrebert),
+[@filtercake](https://github.com/filtercake),
[@ignovak](https://github.com/ignovak),
[@kizu](https://github.com/kizu),
-[@anton-rudeshko](https://github.com/anton-rudeshko),
-[@mishaberezin](https://github.com/mishaberezin)
+[@lefoy](https://github.com/lefoy),
+[@L0stSoul](https://github.com/L0stSoul),
+[@mishaberezin](https://github.com/mishaberezin),
+[@puzankov](https://github.com/puzankov),
+[@schneyra](https://github.com/schneyra),
+[@thejameskyle](https://github.com/thejameskyle),
+[@vecmezoni](https://github.com/vecmezoni)
## License
-This software is released under the terms of the [MIT license](https://github.com/csscomb/csscomb.js/blob/master/LICENSE).
+This software is released under the terms of the
+[MIT license](https://github.com/csscomb/csscomb.js/blob/master/LICENSE).
## Other projects
* https://github.com/senchalabs/cssbeautify
* https://github.com/css/gonzales
+* https://github.com/tonyganch/gonzales-pe
* https://github.com/css/csso
* https://github.com/nzakas/parser-lib
diff --git a/config/csscomb.json b/config/csscomb.json
new file mode 100644
index 00000000..bb27a4eb
--- /dev/null
+++ b/config/csscomb.json
@@ -0,0 +1,373 @@
+{
+ "exclude": [
+ ".git/**",
+ "node_modules/**",
+ "bower_components/**"
+ ],
+ "always-semicolon": true,
+ "block-indent": " ",
+ "color-case": "lower",
+ "color-shorthand": true,
+ "element-case": "lower",
+ "eof-newline": true,
+ "leading-zero": false,
+ "quotes": "single",
+ "remove-empty-rulesets": true,
+ "space-after-colon": " ",
+ "space-after-combinator": " ",
+ "space-after-opening-brace": "\n",
+ "space-after-selector-delimiter": "\n",
+ "space-before-closing-brace": "\n",
+ "space-before-colon": "",
+ "space-before-combinator": " ",
+ "space-before-opening-brace": "\n",
+ "space-before-selector-delimiter": "",
+ "strip-spaces": true,
+ "unitless-zero": true,
+ "vendor-prefix-align": true,
+ "sort-order": [
+ [
+ "font",
+ "font-family",
+ "font-size",
+ "font-weight",
+ "font-style",
+ "font-variant",
+ "font-size-adjust",
+ "font-stretch",
+ "font-effect",
+ "font-emphasize",
+ "font-emphasize-position",
+ "font-emphasize-style",
+ "font-smooth",
+ "line-height"
+ ],
+ [
+ "position",
+ "z-index",
+ "top",
+ "right",
+ "bottom",
+ "left"
+ ],
+ [
+ "display",
+ "visibility",
+ "float",
+ "clear",
+ "overflow",
+ "overflow-x",
+ "overflow-y",
+ "-ms-overflow-x",
+ "-ms-overflow-y",
+ "clip",
+ "zoom",
+ "-webkit-align-content",
+ "-ms-flex-line-pack",
+ "align-content",
+ "-webkit-box-align",
+ "-moz-box-align",
+ "-webkit-align-items",
+ "align-items",
+ "-ms-flex-align",
+ "-webkit-align-self",
+ "-ms-flex-item-align",
+ "-ms-grid-row-align",
+ "align-self",
+ "-webkit-box-flex",
+ "-webkit-flex",
+ "-moz-box-flex",
+ "-ms-flex",
+ "flex",
+ "-webkit-flex-flow",
+ "-ms-flex-flow",
+ "flex-flow",
+ "-webkit-flex-basis",
+ "-ms-flex-preferred-size",
+ "flex-basis",
+ "-webkit-box-orient",
+ "-webkit-box-direction",
+ "-webkit-flex-direction",
+ "-moz-box-orient",
+ "-moz-box-direction",
+ "-ms-flex-direction",
+ "flex-direction",
+ "-webkit-flex-grow",
+ "-ms-flex-positive",
+ "flex-grow",
+ "-webkit-flex-shrink",
+ "-ms-flex-negative",
+ "flex-shrink",
+ "-webkit-flex-wrap",
+ "-ms-flex-wrap",
+ "flex-wrap",
+ "-webkit-box-pack",
+ "-moz-box-pack",
+ "-ms-flex-pack",
+ "-webkit-justify-content",
+ "justify-content",
+ "-webkit-box-ordinal-group",
+ "-webkit-order",
+ "-moz-box-ordinal-group",
+ "-ms-flex-order",
+ "order"
+ ],
+ [
+ "-webkit-box-sizing",
+ "-moz-box-sizing",
+ "box-sizing",
+ "width",
+ "min-width",
+ "max-width",
+ "height",
+ "min-height",
+ "max-height",
+ "margin",
+ "margin-top",
+ "margin-right",
+ "margin-bottom",
+ "margin-left",
+ "padding",
+ "padding-top",
+ "padding-right",
+ "padding-bottom",
+ "padding-left"
+ ],
+ [
+ "table-layout",
+ "empty-cells",
+ "caption-side",
+ "border-spacing",
+ "border-collapse",
+ "list-style",
+ "list-style-position",
+ "list-style-type",
+ "list-style-image"
+ ],
+ [
+ "content",
+ "quotes",
+ "counter-reset",
+ "counter-increment",
+ "resize",
+ "cursor",
+ "-webkit-user-select",
+ "-moz-user-select",
+ "-ms-user-select",
+ "user-select",
+ "nav-index",
+ "nav-up",
+ "nav-right",
+ "nav-down",
+ "nav-left",
+ "-webkit-transition",
+ "-moz-transition",
+ "-ms-transition",
+ "-o-transition",
+ "transition",
+ "-webkit-transition-delay",
+ "-moz-transition-delay",
+ "-ms-transition-delay",
+ "-o-transition-delay",
+ "transition-delay",
+ "-webkit-transition-timing-function",
+ "-moz-transition-timing-function",
+ "-ms-transition-timing-function",
+ "-o-transition-timing-function",
+ "transition-timing-function",
+ "-webkit-transition-duration",
+ "-moz-transition-duration",
+ "-ms-transition-duration",
+ "-o-transition-duration",
+ "transition-duration",
+ "-webkit-transition-property",
+ "-moz-transition-property",
+ "-ms-transition-property",
+ "-o-transition-property",
+ "transition-property",
+ "-webkit-transform",
+ "-moz-transform",
+ "-ms-transform",
+ "-o-transform",
+ "transform",
+ "-webkit-transform-origin",
+ "-moz-transform-origin",
+ "-ms-transform-origin",
+ "-o-transform-origin",
+ "transform-origin",
+ "-webkit-animation",
+ "-moz-animation",
+ "-ms-animation",
+ "-o-animation",
+ "animation",
+ "-webkit-animation-name",
+ "-moz-animation-name",
+ "-ms-animation-name",
+ "-o-animation-name",
+ "animation-name",
+ "-webkit-animation-duration",
+ "-moz-animation-duration",
+ "-ms-animation-duration",
+ "-o-animation-duration",
+ "animation-duration",
+ "-webkit-animation-play-state",
+ "-moz-animation-play-state",
+ "-ms-animation-play-state",
+ "-o-animation-play-state",
+ "animation-play-state",
+ "-webkit-animation-timing-function",
+ "-moz-animation-timing-function",
+ "-ms-animation-timing-function",
+ "-o-animation-timing-function",
+ "animation-timing-function",
+ "-webkit-animation-delay",
+ "-moz-animation-delay",
+ "-ms-animation-delay",
+ "-o-animation-delay",
+ "animation-delay",
+ "-webkit-animation-iteration-count",
+ "-moz-animation-iteration-count",
+ "-ms-animation-iteration-count",
+ "-o-animation-iteration-count",
+ "animation-iteration-count",
+ "-webkit-animation-direction",
+ "-moz-animation-direction",
+ "-ms-animation-direction",
+ "-o-animation-direction",
+ "animation-direction",
+ "text-align",
+ "-webkit-text-align-last",
+ "-moz-text-align-last",
+ "-ms-text-align-last",
+ "text-align-last",
+ "vertical-align",
+ "white-space",
+ "text-decoration",
+ "text-emphasis",
+ "text-emphasis-color",
+ "text-emphasis-style",
+ "text-emphasis-position",
+ "text-indent",
+ "-ms-text-justify",
+ "text-justify",
+ "letter-spacing",
+ "word-spacing",
+ "-ms-writing-mode",
+ "text-outline",
+ "text-transform",
+ "text-wrap",
+ "text-overflow",
+ "-ms-text-overflow",
+ "text-overflow-ellipsis",
+ "text-overflow-mode",
+ "-ms-word-wrap",
+ "word-wrap",
+ "word-break",
+ "-ms-word-break",
+ "-moz-tab-size",
+ "-o-tab-size",
+ "tab-size",
+ "-webkit-hyphens",
+ "-moz-hyphens",
+ "hyphens",
+ "pointer-events"
+ ],
+ [
+ "opacity",
+ "filter:progid:DXImageTransform.Microsoft.Alpha(Opacity",
+ "-ms-filter:\\'progid:DXImageTransform.Microsoft.Alpha",
+ "-ms-interpolation-mode",
+ "color",
+ "border",
+ "border-width",
+ "border-style",
+ "border-color",
+ "border-top",
+ "border-top-width",
+ "border-top-style",
+ "border-top-color",
+ "border-right",
+ "border-right-width",
+ "border-right-style",
+ "border-right-color",
+ "border-bottom",
+ "border-bottom-width",
+ "border-bottom-style",
+ "border-bottom-color",
+ "border-left",
+ "border-left-width",
+ "border-left-style",
+ "border-left-color",
+ "-webkit-border-radius",
+ "-moz-border-radius",
+ "border-radius",
+ "-webkit-border-top-left-radius",
+ "-moz-border-radius-topleft",
+ "border-top-left-radius",
+ "-webkit-border-top-right-radius",
+ "-moz-border-radius-topright",
+ "border-top-right-radius",
+ "-webkit-border-bottom-right-radius",
+ "-moz-border-radius-bottomright",
+ "border-bottom-right-radius",
+ "-webkit-border-bottom-left-radius",
+ "-moz-border-radius-bottomleft",
+ "border-bottom-left-radius",
+ "-webkit-border-image",
+ "-moz-border-image",
+ "-o-border-image",
+ "border-image",
+ "-webkit-border-image-source",
+ "-moz-border-image-source",
+ "-o-border-image-source",
+ "border-image-source",
+ "-webkit-border-image-slice",
+ "-moz-border-image-slice",
+ "-o-border-image-slice",
+ "border-image-slice",
+ "-webkit-border-image-width",
+ "-moz-border-image-width",
+ "-o-border-image-width",
+ "border-image-width",
+ "-webkit-border-image-outset",
+ "-moz-border-image-outset",
+ "-o-border-image-outset",
+ "border-image-outset",
+ "-webkit-border-image-repeat",
+ "-moz-border-image-repeat",
+ "-o-border-image-repeat",
+ "border-image-repeat",
+ "outline",
+ "outline-width",
+ "outline-style",
+ "outline-color",
+ "outline-offset",
+ "background",
+ "filter:progid:DXImageTransform.Microsoft.AlphaImageLoader",
+ "background-color",
+ "background-image",
+ "background-repeat",
+ "background-attachment",
+ "background-position",
+ "background-position-x",
+ "-ms-background-position-x",
+ "background-position-y",
+ "-ms-background-position-y",
+ "-webkit-background-clip",
+ "-moz-background-clip",
+ "background-clip",
+ "background-origin",
+ "-webkit-background-size",
+ "-moz-background-size",
+ "-o-background-size",
+ "background-size",
+ "box-decoration-break",
+ "-webkit-box-shadow",
+ "-moz-box-shadow",
+ "box-shadow",
+ "filter:progid:DXImageTransform.Microsoft.gradient",
+ "-ms-filter:\\'progid:DXImageTransform.Microsoft.gradient",
+ "text-shadow"
+ ]
+ ]
+}
diff --git a/.csscomb.json b/config/yandex.json
similarity index 85%
rename from .csscomb.json
rename to config/yandex.json
index 883e1ac4..ec6c5554 100644
--- a/.csscomb.json
+++ b/config/yandex.json
@@ -1,40 +1,10 @@
{
"exclude": [
".git/**",
- "node_modules/**"
+ "node_modules/**",
+ "bower_components/**"
],
- "always-semicolon": true,
- "block-indent": true,
- "colon-space": true,
- "color-case": "lower",
- "color-shorthand": true,
- "combinator-space": true,
- "element-case": "lower",
- "eof-newline": true,
- "leading-zero": false,
- "remove-empty-rulesets": true,
- "rule-indent": true,
- "stick-brace": "\n",
- "strip-spaces": true,
- "unitless-zero": true,
- "vendor-prefix-align": true,
"sort-order": [
- [
- "font",
- "font-family",
- "font-size",
- "font-weight",
- "font-style",
- "font-variant",
- "font-size-adjust",
- "font-stretch",
- "font-effect",
- "font-emphasize",
- "font-emphasize-position",
- "font-emphasize-style",
- "font-smooth",
- "line-height"
- ],
[
"position",
"z-index",
@@ -53,12 +23,58 @@
"overflow-y",
"-ms-overflow-x",
"-ms-overflow-y",
+ "-webkit-overflow-scrolling",
"clip",
"zoom",
+ "-webkit-align-content",
+ "-ms-flex-line-pack",
+ "align-content",
+ "-webkit-box-align",
+ "-moz-box-align",
+ "-webkit-align-items",
+ "align-items",
+ "-ms-flex-align",
+ "-webkit-align-self",
+ "-ms-flex-item-align",
+ "-ms-grid-row-align",
+ "align-self",
+ "-webkit-box-flex",
+ "-webkit-flex",
+ "-moz-box-flex",
+ "-ms-flex",
+ "flex",
+ "-webkit-flex-flow",
+ "-ms-flex-flow",
+ "flex-flow",
+ "-webkit-flex-basis",
+ "-ms-flex-preferred-size",
+ "flex-basis",
+ "-webkit-box-orient",
+ "-webkit-box-direction",
+ "-webkit-flex-direction",
+ "-moz-box-orient",
+ "-moz-box-direction",
+ "-ms-flex-direction",
"flex-direction",
- "flex-order",
- "flex-pack",
- "flex-align"
+ "-webkit-flex-grow",
+ "-ms-flex-positive",
+ "flex-grow",
+ "-webkit-flex-shrink",
+ "-ms-flex-negative",
+ "flex-shrink",
+ "-webkit-flex-wrap",
+ "-ms-flex-wrap",
+ "flex-wrap",
+ "-webkit-box-pack",
+ "-moz-box-pack",
+ "-ms-flex-pack",
+ "-webkit-justify-content",
+ "justify-content",
+ "-webkit-box-ordinal-group",
+ "-webkit-order",
+ "-moz-box-ordinal-group",
+ "-ms-flex-order",
+ "order"
],
[
"-webkit-box-sizing",
@@ -198,6 +214,7 @@
"text-indent",
"-ms-text-justify",
"text-justify",
+ "text-transform",
"letter-spacing",
"word-spacing",
"-ms-writing-mode",
@@ -227,6 +244,7 @@
"-ms-interpolation-mode",
"color",
"border",
+ "border-collapse",
"border-width",
"border-style",
"border-color",
@@ -316,6 +334,22 @@
"filter:progid:DXImageTransform.Microsoft.gradient",
"-ms-filter:\\'progid:DXImageTransform.Microsoft.gradient",
"text-shadow"
+ ],
+ [
+ "font",
+ "font-family",
+ "font-size",
+ "font-weight",
+ "font-style",
+ "font-variant",
+ "font-size-adjust",
+ "font-stretch",
+ "font-effect",
+ "font-emphasize",
+ "font-emphasize-position",
+ "font-emphasize-style",
+ "font-smooth",
+ "line-height"
]
]
}
diff --git a/config/zen.json b/config/zen.json
new file mode 100644
index 00000000..9f462b96
--- /dev/null
+++ b/config/zen.json
@@ -0,0 +1,413 @@
+{
+ "exclude": [
+ ".git/**",
+ "node_modules/**",
+ "bower_components/**"
+ ],
+ "sort-order": [ [
+ "position",
+ "top",
+ "right",
+ "bottom",
+ "left",
+ "z-index",
+ "display",
+ "visibility",
+ "float",
+ "clear",
+ "overflow",
+ "-ms-overflow-x",
+ "-ms-overflow-y",
+ "overflow-x",
+ "overflow-y",
+ "-webkit-overflow-scrolling",
+ "clip",
+ "-webkit-align-content",
+ "-ms-flex-line-pack",
+ "align-content",
+ "-webkit-box-align",
+ "-moz-box-align",
+ "-webkit-align-items",
+ "align-items",
+ "-ms-flex-align",
+ "-webkit-align-self",
+ "-ms-flex-item-align",
+ "-ms-grid-row-align",
+ "align-self",
+ "-webkit-box-flex",
+ "-webkit-flex",
+ "-moz-box-flex",
+ "-ms-flex",
+ "flex",
+ "-webkit-flex-flow",
+ "-ms-flex-flow",
+ "flex-flow",
+ "-webkit-flex-basis",
+ "-ms-flex-preferred-size",
+ "flex-basis",
+ "-webkit-box-orient",
+ "-webkit-box-direction",
+ "-webkit-flex-direction",
+ "-moz-box-orient",
+ "-moz-box-direction",
+ "-ms-flex-direction",
+ "flex-direction",
+ "-webkit-flex-grow",
+ "-ms-flex-positive",
+ "flex-grow",
+ "-webkit-flex-shrink",
+ "-ms-flex-negative",
+ "flex-shrink",
+ "-webkit-flex-wrap",
+ "-ms-flex-wrap",
+ "flex-wrap",
+ "-webkit-box-pack",
+ "-moz-box-pack",
+ "-ms-flex-pack",
+ "-webkit-justify-content",
+ "justify-content",
+ "-webkit-box-ordinal-group",
+ "-webkit-order",
+ "-moz-box-ordinal-group",
+ "-ms-flex-order",
+ "order",
+ "-webkit-box-sizing",
+ "-moz-box-sizing",
+ "box-sizing",
+ "margin",
+ "margin-top",
+ "margin-right",
+ "margin-bottom",
+ "margin-left",
+ "padding",
+ "padding-top",
+ "padding-right",
+ "padding-bottom",
+ "padding-left",
+ "min-width",
+ "min-height",
+ "max-width",
+ "max-height",
+ "width",
+ "height",
+ "outline",
+ "outline-width",
+ "outline-style",
+ "outline-color",
+ "outline-offset",
+ "border",
+ "border-spacing",
+ "border-collapse",
+ "border-width",
+ "border-style",
+ "border-color",
+ "border-top",
+ "border-top-width",
+ "border-top-style",
+ "border-top-color",
+ "border-right",
+ "border-right-width",
+ "border-right-style",
+ "border-right-color",
+ "border-bottom",
+ "border-bottom-width",
+ "border-bottom-style",
+ "border-bottom-color",
+ "border-left",
+ "border-left-width",
+ "border-left-style",
+ "border-left-color",
+ "-webkit-border-radius",
+ "-moz-border-radius",
+ "border-radius",
+ "-webkit-border-top-left-radius",
+ "-moz-border-radius-topleft",
+ "border-top-left-radius",
+ "-webkit-border-top-right-radius",
+ "-moz-border-radius-topright",
+ "border-top-right-radius",
+ "-webkit-border-bottom-right-radius",
+ "-moz-border-radius-bottomright",
+ "border-bottom-right-radius",
+ "-webkit-border-bottom-left-radius",
+ "-moz-border-radius-bottomleft",
+ "border-bottom-left-radius",
+ "-webkit-border-image",
+ "-moz-border-image",
+ "-o-border-image",
+ "border-image",
+ "-webkit-border-image-source",
+ "-moz-border-image-source",
+ "-o-border-image-source",
+ "border-image-source",
+ "-webkit-border-image-slice",
+ "-moz-border-image-slice",
+ "-o-border-image-slice",
+ "border-image-slice",
+ "-webkit-border-image-width",
+ "-moz-border-image-width",
+ "-o-border-image-width",
+ "border-image-width",
+ "-webkit-border-image-outset",
+ "-moz-border-image-outset",
+ "-o-border-image-outset",
+ "border-image-outset",
+ "-webkit-border-image-repeat",
+ "-moz-border-image-repeat",
+ "-o-border-image-repeat",
+ "border-image-repeat",
+ "-webkit-border-top-image",
+ "-moz-border-top-image",
+ "-o-border-top-image",
+ "border-top-image",
+ "-webkit-border-right-image",
+ "-moz-border-right-image",
+ "-o-border-right-image",
+ "border-right-image",
+ "-webkit-border-bottom-image",
+ "-moz-border-bottom-image",
+ "-o-border-bottom-image",
+ "border-bottom-image",
+ "-webkit-border-left-image",
+ "-moz-border-left-image",
+ "-o-border-left-image",
+ "border-left-image",
+ "-webkit-border-corner-image",
+ "-moz-border-corner-image",
+ "-o-border-corner-image",
+ "border-corner-image",
+ "-webkit-border-top-left-image",
+ "-moz-border-top-left-image",
+ "-o-border-top-left-image",
+ "border-top-left-image",
+ "-webkit-border-top-right-image",
+ "-moz-border-top-right-image",
+ "-o-border-top-right-image",
+ "border-top-right-image",
+ "-webkit-border-bottom-right-image",
+ "-moz-border-bottom-right-image",
+ "-o-border-bottom-right-image",
+ "border-bottom-right-image",
+ "-webkit-border-bottom-left-image",
+ "-moz-border-bottom-left-image",
+ "-o-border-bottom-left-image",
+ "border-bottom-left-image",
+ "background",
+ "filter:progid:DXImageTransform.Microsoft.AlphaImageLoader",
+ "background-color",
+ "background-image",
+ "background-attachment",
+ "background-position",
+ "-ms-background-position-x",
+ "-ms-background-position-y",
+ "background-position-x",
+ "background-position-y",
+ "-webkit-background-clip",
+ "-moz-background-clip",
+ "background-clip",
+ "background-origin",
+ "-webkit-background-size",
+ "-moz-background-size",
+ "-o-background-size",
+ "background-size",
+ "background-repeat",
+ "box-decoration-break",
+ "-webkit-box-shadow",
+ "-moz-box-shadow",
+ "box-shadow",
+ "color",
+ "table-layout",
+ "caption-side",
+ "empty-cells",
+ "list-style",
+ "list-style-position",
+ "list-style-type",
+ "list-style-image",
+ "quotes",
+ "content",
+ "counter-increment",
+ "counter-reset",
+ "-ms-writing-mode",
+ "vertical-align",
+ "text-align",
+ "-webkit-text-align-last",
+ "-moz-text-align-last",
+ "-ms-text-align-last",
+ "text-align-last",
+ "text-decoration",
+ "text-emphasis",
+ "text-emphasis-position",
+ "text-emphasis-style",
+ "text-emphasis-color",
+ "text-indent",
+ "-ms-text-justify",
+ "text-justify",
+ "text-outline",
+ "text-transform",
+ "text-wrap",
+ "-ms-text-overflow",
+ "text-overflow",
+ "text-overflow-ellipsis",
+ "text-overflow-mode",
+ "text-shadow",
+ "white-space",
+ "word-spacing",
+ "-ms-word-wrap",
+ "word-wrap",
+ "-ms-word-break",
+ "word-break",
+ "-moz-tab-size",
+ "-o-tab-size",
+ "tab-size",
+ "-webkit-hyphens",
+ "-moz-hyphens",
+ "hyphens",
+ "letter-spacing",
+ "font",
+ "font-weight",
+ "font-style",
+ "font-variant",
+ "font-size-adjust",
+ "font-stretch",
+ "font-size",
+ "font-family",
+ "src",
+ "line-height",
+ "opacity",
+ "-ms-filter:\\'progid:DXImageTransform.Microsoft.Alpha",
+ "filter:progid:DXImageTransform.Microsoft.Alpha(Opacity",
+ "-ms-interpolation-mode",
+ "-webkit-filter",
+ "-ms-filter",
+ "filter",
+ "resize",
+ "cursor",
+ "nav-index",
+ "nav-up",
+ "nav-right",
+ "nav-down",
+ "nav-left",
+ "-webkit-transition",
+ "-moz-transition",
+ "-ms-transition",
+ "-o-transition",
+ "transition",
+ "-webkit-transition-delay",
+ "-moz-transition-delay",
+ "-ms-transition-delay",
+ "-o-transition-delay",
+ "transition-delay",
+ "-webkit-transition-timing-function",
+ "-moz-transition-timing-function",
+ "-ms-transition-timing-function",
+ "-o-transition-timing-function",
+ "transition-timing-function",
+ "-webkit-transition-duration",
+ "-moz-transition-duration",
+ "-ms-transition-duration",
+ "-o-transition-duration",
+ "transition-duration",
+ "-webkit-transition-property",
+ "-moz-transition-property",
+ "-ms-transition-property",
+ "-o-transition-property",
+ "transition-property",
+ "-webkit-transform",
+ "-moz-transform",
+ "-ms-transform",
+ "-o-transform",
+ "transform",
+ "-webkit-transform-origin",
+ "-moz-transform-origin",
+ "-ms-transform-origin",
+ "-o-transform-origin",
+ "transform-origin",
+ "-webkit-animation",
+ "-moz-animation",
+ "-ms-animation",
+ "-o-animation",
+ "animation",
+ "-webkit-animation-name",
+ "-moz-animation-name",
+ "-ms-animation-name",
+ "-o-animation-name",
+ "animation-name",
+ "-webkit-animation-duration",
+ "-moz-animation-duration",
+ "-ms-animation-duration",
+ "-o-animation-duration",
+ "animation-duration",
+ "-webkit-animation-play-state",
+ "-moz-animation-play-state",
+ "-ms-animation-play-state",
+ "-o-animation-play-state",
+ "animation-play-state",
+ "-webkit-animation-timing-function",
+ "-moz-animation-timing-function",
+ "-ms-animation-timing-function",
+ "-o-animation-timing-function",
+ "animation-timing-function",
+ "-webkit-animation-delay",
+ "-moz-animation-delay",
+ "-ms-animation-delay",
+ "-o-animation-delay",
+ "animation-delay",
+ "-webkit-animation-iteration-count",
+ "-moz-animation-iteration-count",
+ "-ms-animation-iteration-count",
+ "-o-animation-iteration-count",
+ "animation-iteration-count",
+ "-webkit-animation-direction",
+ "-moz-animation-direction",
+ "-ms-animation-direction",
+ "-o-animation-direction",
+ "animation-direction",
+ "pointer-events",
+ "unicode-bidi",
+ "direction",
+ "-webkit-columns",
+ "-moz-columns",
+ "columns",
+ "-webkit-column-span",
+ "-moz-column-span",
+ "column-span",
+ "-webkit-column-width",
+ "-moz-column-width",
+ "column-width",
+ "-webkit-column-count",
+ "-moz-column-count",
+ "column-count",
+ "-webkit-column-fill",
+ "-moz-column-fill",
+ "column-fill",
+ "-webkit-column-gap",
+ "-moz-column-gap",
+ "column-gap",
+ "-webkit-column-rule",
+ "-moz-column-rule",
+ "column-rule",
+ "-webkit-column-rule-width",
+ "-moz-column-rule-width",
+ "column-rule-width",
+ "-webkit-column-rule-style",
+ "-moz-column-rule-style",
+ "column-rule-style",
+ "-webkit-column-rule-color",
+ "-moz-column-rule-color",
+ "column-rule-color",
+ "break-before",
+ "break-inside",
+ "break-after",
+ "page-break-before",
+ "page-break-inside",
+ "page-break-after",
+ "orphans",
+ "widows",
+ "-ms-zoom",
+ "zoom",
+ "max-zoom",
+ "min-zoom",
+ "user-zoom",
+ "orientation"
+ ] ]
+}
diff --git a/doc/configuration.md b/doc/configuration.md
new file mode 100644
index 00000000..2d36135e
--- /dev/null
+++ b/doc/configuration.md
@@ -0,0 +1,133 @@
+# Configuration
+
+You must configure CSScomb before use.
+There are a number of ways how you can do it.
+
+## Use one of predefined configs
+
+There are [several config
+files](https://github.com/csscomb/csscomb.js/tree/master/config)
+included in this project you can use right away:
+
+- `csscomb`
+- `zen`
+- `yandex`
+
+In CLI, `csscomb` is a default config file that is used unless you provide your
+own.
+In Node.js, you can pass config's name to constructor:
+
+```js
+var Comb = require('csscomb');
+var comb = new Comb('yandex');
+```
+
+Feel free to use predefined configs as examples: copy one of them and modify to
+your taste.
+Just remember to save the file as `.csscomb.json` in project's root.
+
+## Create custom config
+
+You can easily write your own configuration.
+The only requirement is that config is valid JSON in order to work correctly.
+
+Here is an example:
+
+```json
+{
+ "exclude": ["node_modules/**"],
+ "verbose": true,
+
+ "always-semicolon": true,
+ "color-case": "lower",
+ "color-shorthand": true,
+ "element-case": "lower",
+ "eof-newline": true,
+ "leading-zero": false,
+ "quotes": "single",
+ "remove-empty-rulesets": true,
+ "strip-spaces": true,
+ "unitless-zero": true,
+ "vendor-prefix-align": true
+}
+```
+
+Take a look at [available options](options.md) and choose those you need. You can use our [visual config builder](http://csscomb.com/config) to choose most options.
+
+### Where to put config
+
+CSScomb will look for a file named `.csscomb.json`.
+The best way is to put the file in your project's root.
+However, if you want to use one config for several projects, it's fine to put
+the file inside a parent folder.
+CSScomb will look for a config file recursively up untill it reaches your
+`HOME` directory.
+
+Remember that you can always set custom path.
+In CLI:
+```bash
+csscomb -c path/to/config assets/css
+```
+
+In Node.js:
+```js
+var Comb = require('csscomb');
+var config = require('path/to/config');
+var comb = new Comb(config);
+```
+
+## Generate config from a template
+
+Instead of configuring all the options one by one, you can use a template file:
+CSScomb will detect the coding style and use it as a config.
+All existing properties except `sort-order` can be configured this way:
+
+```bash
+csscomb -d example.css > .csscomb.json
+```
+
+This will create `.csscomb.json` based on options that can be detected in
+`example.css` file.
+
+Let's say your template file has following content:
+
+```css
+.foo
+{
+ width: #fff;
+}
+```
+
+Generated config wiil then look this way:
+
+```json
+{
+ "remove-empty-rulesets": true,
+ "always-semicolon": true,
+ "color-case": "lower",
+ "color-shorthand": true,
+ "strip-spaces": true,
+ "eof-newline": true
+}
+```
+
+
+## Override template's settings
+
+You can use template inside existing config and then complete it or override
+some of detected settings:
+
+```json
+{
+ "template": "example.css",
+ "leading-zero": false,
+ "vendor-prefix-align": true
+}
+```
+
+This config will:
+
+1. detect all the options from the `example.css`,
+1. then use `"leading-zero": false` instead of anything detected,
+1. then use `"vendor-prefix-align": true` even if there were no prefixed
+properties or values inside the `example.css`.
diff --git a/doc/options.md b/doc/options.md
new file mode 100644
index 00000000..959e9291
--- /dev/null
+++ b/doc/options.md
@@ -0,0 +1,974 @@
+# Configuration options
+
+There are a number of options you can use, all of them are switched off by
+default.
+Here is a full list in the same order they are applied while processing css:
+
+- [always-semicolon](#always-semicolon)
+- [remove-empty-rulesets](#remove-empty-rulesets)
+- [color-case](#color-case)
+- [color-shorthand](#color-shorthand)
+- [element-case](#element-case)
+- [eof-newline](#eof-newline)
+- [leading-zero](#leading-zero)
+- [quotes](#quotes)
+- [sort-order-fallback](#sort-order-fallback)
+- [space-after-colon](#space-after-colon)
+- [space-after-combinator](#space-after-combinator)
+- [space-after-opening-brace](#space-after-opening-brace)
+- [space-after-selector-delimiter](#space-after-selector-delimiter)
+- [space-before-colon](#space-before-colon)
+- [space-before-combinator](#space-before-combinator)
+- [space-before-opening-brace](#space-before-opening-brace)
+- [space-before-selector-delimiter](#space-before-selector-delimiter)
+- [space-between-declarations](#space-between-declarations)
+- [block-indent](#block-indent)
+- [sort-order](#sort-order)
+- [strip-spaces](#strip-spaces)
+- [space-before-closing-brace](#space-before-closing-brace)
+- [unitless-zero](#unitless-zero)
+- [tab-size](#tab-size)
+- [vendor-prefix-align](#vendor-prefix-align)
+- [lines-between-rulesets](#lines-between-rulesets)
+
+Following options are ignored while processing `*.sass` files:
+
+- always-semicolon
+- space-before-opening-brace
+- space-after-opening-brace
+- space-before-closing-brace
+- space-between-declarations
+
+
+## always-semicolon
+
+Whether to add a semicolon after the *last* value/mixin.
+
+Acceptable value: `true`.
+
+Example: `{ "always-semicolon": true }`
+
+```css
+/* before */
+a { color: red; text-decoration: underline }
+
+/* after */
+a { color: red; text-decoration: underline; }
+```
+
+### always-semicolon vs. preprocessors
+
+In `*.scss` and `*.less` files semicolons are not added after `}`
+even if it's part of a value.
+
+Example: `{ "always-semicolon": true }`
+
+```scss
+// before
+div {
+ color: tomato;
+ font: {
+ family: fantasy;
+ size: 16px
+ }
+ }
+
+// after
+div {
+ color: tomato;
+ font: {
+ family: fantasy;
+ size: 16px;
+ }
+ }
+```
+
+## block-indent
+
+Set indent for code inside blocks, including media queries and nested rules.
+
+Acceptable values:
+
+* `{Number}` — number of whitespaces;
+* `{String}` — string with whitespaces and tabs. Note that line breaks are not
+ allowed here.
+
+Example: `{ "block-indent": 4 }`
+
+```scss
+// Before:
+a {
+top: 0;
+ p {
+ color: tomato;
+position: happy;
+ }
+}
+
+// After:
+a {
+ top: 0;
+ p {
+ color: tomato;
+ position: happy;
+ }
+ }
+```
+
+Example: `{ "block-indent": "" }`
+
+```scss
+// Before:
+a {
+top: 0;
+ p {
+ color: tomato;
+position: happy;
+ }
+}
+
+// After:
+a {
+top: 0;
+p {
+color: tomato;
+position: happy;
+}
+}
+```
+
+## color-case
+
+Unify case of hexadecimal colors.
+
+Acceptable values:
+
+* `lower` — for lowercase,
+* `upper` — for uppercase.
+
+Example: `{ "color-case": "lower" }`
+
+```css
+/* before */
+a { color: #FFF }
+
+/* after */
+a { color: #fff }
+```
+
+## color-shorthand
+
+Whether to expand hexadecimal colors or use shorthands.
+
+Acceptable values:
+
+* `true` — use shorthands;
+* `false` — expand hexadecimal colors to 6 symbols.
+
+Example: `{ "color-shorthand": true }`
+
+```css
+/* before */
+b { color: #ffcc00 }
+
+/* after */
+b { color: #fc0 }
+```
+
+Example: `{ "color-shorthand": false }`
+
+```css
+/* before */
+b { color: #fc0 }
+
+/* after */
+b { color: #ffcc00 }
+```
+
+## element-case
+
+Unify case of element selectors.
+
+Acceptable values:
+
+* `lower` — for lowercase;
+* `upper` — for uppercase.
+
+Example: `{ "element-case": "upper" }`
+
+```css
+/* before */
+li > a { color: red }
+
+/* after */
+LI > A { color: red }
+```
+
+## eof-newline
+
+Add/remove line break at EOF.
+
+Acceptable values:
+
+* `true` — add line break;
+* `false` – remove line break.
+
+Example: `{ "eof-newline": true }`
+
+`a { color: red }` → `a { color: red }\n`
+
+Example: `{ "eof-newline": false }`
+
+`a { color: red }\n` → `a { color: red }`
+
+## exclude
+
+List files that should be ignored while combing.
+
+Acceptable value:
+
+* `{String[]}` — array of
+ [Ant path patterns](http://ant.apache.org/manual/dirtasks.html#patterns).
+
+Example: `{ "exclude": ["node_modules/**"] }` — exclude all files and
+directories under `node_modules` dir.
+
+## leading-zero
+
+Add/remove leading zero in dimensions.
+
+Acceptable values:
+
+* `true` — add leading zero;
+* `false` — remove leading zero.
+
+Example: `{ "leading-zero": false }`
+
+```css
+/* before */
+p { padding: 0.5em }
+
+/* after */
+p { padding: .5em }
+```
+
+## quotes
+
+Unify quotes style.
+
+Acceptable values:
+
+* `single` — transform all `"` to `'`;
+* `double` — transform all `'` to `"`.
+
+Example: `{ "quotes": "single" }`
+
+```css
+/* before */
+p[href^="https://"]:before { content: "secure" }
+
+/* after */
+p[href^='https://']:before { content: 'secure' }
+```
+
+## remove-empty-rulesets
+
+Remove all rulesets that contain nothing but spaces.
+
+Acceptable value: `true`
+
+Example: `{ "remove-empty-rulesets": true }`.
+
+`a { color: red; } p { /* hey */ } b { }` → `a { color: red; } p { /* hey */ } `
+
+## sort-order
+
+Set sort order.
+
+**Note**: Use one of [predefined
+configs](https://github.com/csscomb/csscomb.js/tree/master/config)
+as an example.
+
+Acceptable values:
+
+* `{Array}` of rules
+* `{Array}` of arrays of rules for groups separation
+
+Example: `{ "sort-order": [ "margin", "padding" ] }`
+
+```css
+/* before */
+p {
+ padding: 0;
+ margin: 0;
+}
+
+/* after */
+p {
+ margin: 0;
+ padding: 0;
+}
+```
+
+Example: `{ "sort-order": [ [ "margin", "padding" ], [ "border", "background" ] ] }`
+
+```css
+/* before */
+p {
+ background: none;
+ border: 0;
+ margin: 0;
+ padding: 0;
+}
+
+/* after */
+p {
+ margin: 0;
+ padding: 0;
+
+ border: 0;
+ background: none;
+}
+```
+
+### sort-order vs. preprocessors
+
+If you sort properties in `*.scss` or `*.less` files, you can use one of 3
+keywords in your config:
+
+* `$variable` — for variable declarations (e.g. `$var` in Sass or `@var` in LESS);
+* `$include` — for all mixins except those that have been specified (e.g. `@include ...` in Sass
+ or `.mixin()` in LESS);
+* `$include mixin-name` — for mixins with specified name (e.g. `@include mixin-name` in Sass
+ or `.mixin-name()` in LESS);
+* `$extend` — for extends (e.g. `@extend .foo` in Sass or `&:extend(.foo)` in LESS);
+* `$import` — for `@import` rules.
+
+Example: `{ "sort-order": [ [ "$variable" ], [ "$include" ], [ "top", "padding" ], [ "$include media" ] ] }`
+
+```scss
+/* before */
+p {
+ padding: 0;
+ @include mixin($color);
+ $color: tomato;
+ @include media("desktop") {
+ color: black;
+ }
+ top: 0;
+}
+
+/* after */
+p {
+ $color: tomato;
+
+ @include mixin($color);
+
+ top: 0;
+ padding: 0;
+
+ @include media("desktop") {
+ color: black;
+ }
+}
+```
+
+### sort-order vs. leftovers
+
+When there are properties that are not mentioned in the `sort-order` option, they are inserted after all the sorted properties in the new group in the same order they were in the source stylesheet.
+
+You can override this by using a “leftovers” token: `...` — just place it either in its own group, or near other properties in any other group and CSSComb would place all the properties that were not sorted where the `...` was in `sort-order`.
+
+So, with this value:
+
+``` json
+{
+ "sort-order": [
+ ["$variable"],
+ ["position"],
+ ["...", "border"],
+ ["$include"],
+ ["font"]
+ ]
+}
+```
+
+everything would go into five groups: variables, then group with `position`, then group containing all the leftovers plus the `border`, then group with all includes and then the `font`.
+
+## sort-order-fallback
+
+Apply a special sort order for properties that are not specified in `sort-order`
+list.
+Works great with [leftovers](#sort-order-vs-leftovers).
+**Note:** This option is applied only if [sort order](#sort-order) list is
+provided.
+
+Acceptable values:
+
+* `abc` - sort unknown options alphabetically.
+
+Example: `{ "sort-order-fallback": "abc", "sort-order": ["top"] }`
+
+```scss
+// Before:
+a {
+ height: 100px;
+ color: tomato;
+ top: 0;
+}
+
+// After:
+a {
+ top:0;
+
+ color:tomato;
+ height: 100px;
+}
+```
+
+Example: `{ "sort-order-fallback": "abc", "sort-order": ["..."] }`
+
+```scss
+// Before:
+a {
+ height: 100px;
+ color: tomato;
+ top: 0;
+}
+
+// After:
+a {
+ color:tomato;
+ height: 100px;
+ top:0;
+}
+```
+
+## space-after-colon
+
+Set space after `:` in declarations.
+
+Acceptable values:
+
+* `{Number}` — number of whitespaces;
+* `{String}` — string with whitespaces, tabs or line breaks.
+
+Example: `{ "space-after-colon": "" }`
+
+```scss
+// Before:
+a {
+ top: 0;
+ color: tomato;
+}
+
+// After:
+a {
+ top:0;
+ color:tomato;
+}
+```
+
+Example: `{ "space-after-colon": 1 }`
+
+```scss
+// Before:
+a {
+ top:0;
+ color:tomato;
+}
+
+// After:
+a {
+ top: 0;
+ color: tomato;
+}
+```
+
+## space-after-combinator
+
+Set space after combinator (for example, in selectors like `p > a`).
+
+Acceptable values:
+
+* `{Number}` — number of whitespaces;
+* `{String}` — string with whitespaces, tabs or line breaks.
+
+Example: `{ "space-after-combinator": 1 }`
+
+```scss
+// Before:
+p>a { color: panda; }
+
+// After:
+p> a { color: panda; }
+```
+
+Example: `{ "space-after-combinator": "\n " }`
+
+```scss
+// Before:
+p > a { color: panda; }
+
+// After:
+p >
+ a { color: panda; }
+```
+
+## space-between-declarations
+
+Set space between declarations (i.e. `color: tomato`).
+
+Acceptable values:
+
+* `{Number}` — number of whitespaces;
+* `{String}` — string with whitespaces, tabs or line breaks.
+
+Example: `{ "space-between-declarations": 1 }`
+
+```scss
+// Before:
+a {
+ color: panda; /* comment */
+ top: 0;
+ /* comment */
+ right: 0;
+ position: absolute
+ }
+
+// After:
+a {
+ color: panda; /* comment */ top: 0;
+ /* comment */
+ right: 0; position: absolute
+ }
+```
+
+Example: `{ "space-between-declarations": "\n " }`
+
+```scss
+// Before:
+a {
+ color: panda; top: 0; right: 0}
+
+// After:
+a {
+ color: panda;
+ top: 0;
+ right: 0;}
+```
+
+
+## space-after-opening-brace
+
+Set space after `{`.
+
+Acceptable values:
+
+* `{Number}` — number of whitespaces;
+* `{String}` — string with whitespaces, tabs or line breaks.
+
+Example: `{ "space-after-opening-brace": 1 }`
+
+```scss
+// Before:
+a {color: panda;}
+
+// After:
+a { color: panda;}
+```
+
+Example: `{ "space-after-opening-brace": "\n" }`
+
+```scss
+// Before:
+a{color: panda;}
+
+// After:
+a{
+color: panda;}
+```
+
+## space-after-selector-delimiter
+
+Set space after selector delimiter.
+
+Acceptable values:
+
+* `{Number}` — number of whitespaces;
+* `{String}` — string with whitespaces, tabs or line breaks.
+
+Example: `{ "space-after-selector-delimiter": 1 }`
+
+```scss
+// Before:
+a,b{
+ color: panda;
+ }
+
+// After:
+a, b {
+ color: panda;
+ }
+```
+
+Example: `{ "space-after-selector-delimiter": "\n" }`
+
+```scss
+// Before:
+a, b{
+ color: panda;
+ }
+
+// After:
+a,
+b{
+ color: panda;
+ }
+```
+
+## space-before-closing-brace
+
+Set space before `}`.
+
+Acceptable values:
+
+* `{Number}` — number of whitespaces;
+* `{String}` — string with whitespaces, tabs or line breaks.
+
+Example: `{ "space-before-closing-brace": 1 }`
+
+```scss
+// Before:
+a {
+ top: 0;
+ color: tomato;
+}
+
+// After:
+a {
+ top: 0;
+ color: tomato; }
+```
+
+Example: `{ "space-before-closing-brace": "\n" }`
+
+```scss
+// Before:
+a {
+ top: 0;
+ color: tomato;}
+
+// After:
+a {
+ top: 0;
+ color: tomato;
+}
+```
+
+## space-before-colon
+
+Set space before `:` in declarations.
+
+Acceptable values:
+
+* `{Number}` — number of whitespaces;
+* `{String}` — string with whitespaces, tabs or line breaks.
+
+Example: `{ "space-before-colon": "" }`
+
+```scss
+// Before:
+a {
+ top : 0;
+ color : tomato;
+}
+
+// After:
+a {
+ top: 0;
+ color: tomato;
+}
+```
+
+Example: `{ "space-before-colon": 1 }`
+
+```scss
+// Before:
+a {
+ top:0;
+ color:tomato;
+}
+
+// After:
+a {
+ top :0;
+ color :tomato;
+}
+```
+
+## space-before-combinator
+
+Set space before combinator (for example, in selectors like `p > a`).
+
+Acceptable values:
+
+* `{Number}` — number of whitespaces;
+* `{String}` — string with whitespaces, tabs or line breaks.
+
+Example: `{ "space-before-combinator": 1 }`
+
+```scss
+// Before:
+p>a { color: panda; }
+
+// After:
+p >a { color: panda; }
+```
+
+Example: `{ "space-before-combinator": "\n" }`
+
+```scss
+// Before:
+p > a { color: panda; }
+
+// After:
+p
+> a { color: panda; }
+```
+
+## space-before-opening-brace
+
+Set space before `{`.
+
+Acceptable values:
+
+* `{Number}` — number of whitespaces;
+* `{String}` — string with whitespaces, tabs or line breaks.
+
+Example: `{ "space-before-opening-brace": 1 }`
+
+```scss
+// Before:
+a{
+ color: panda;
+ }
+
+// After:
+a {
+ color: panda;
+ }
+```
+
+Example: `{ "space-before-opening-brace": "\n" }`
+
+```scss
+// Before:
+a{
+ color: panda;
+ }
+
+// After:
+a
+{
+ color: panda;
+ }
+```
+
+## space-before-selector-delimiter
+
+Set space before selector delimiter.
+
+Acceptable values:
+
+* `{Number}` — number of whitespaces;
+* `{String}` — string with whitespaces, tabs or line breaks.
+
+Example: `{ "space-before-selector-delimiter": 0 }`
+
+```scss
+// Before:
+a , b{
+ color: panda;
+ }
+
+// After:
+a, b {
+ color: panda;
+ }
+```
+
+Example: `{ "space-before-selector-delimiter": "\n" }`
+
+```scss
+// Before:
+a, b{
+ color: panda;
+ }
+
+// After:
+a
+,b{
+ color: panda;
+ }
+```
+
+## strip-spaces
+
+Whether to trim trailing spaces.
+
+Acceptable value: `true`.
+
+Example: `{ "strip-spaces": true }`
+
+`a { color: red } \n \n \n` → `a { color: red }\n`
+
+`a { color: red }\t` → `a { color: red }`
+
+## tab-size
+
+Set tab size (number of spaces to replace hard tabs).
+
+Acceptable values:
+
+* `{Number}` — number of whitespaces;
+
+Example: `{ "tab-size": 2 }`
+
+```scss
+// Before:
+a{
+ color: panda;
+ }
+
+// After:
+a {
+ color: panda;
+ }
+```
+
+## template
+
+**Note:** See [configuration docs](configuration.md#override-templates-settings)
+for more information.
+
+Acceptable value:
+
+* `{String}` — path to the `.css` file.
+
+Example: `{ "template": "example.css" }`
+
+## unitless-zero
+
+Whether to remove units in zero-valued dimensions.
+
+Acceptable value: `true`.
+
+Example: `{ "unitless-zero": true }`
+
+```css
+/* before */
+img { border: 0px }
+
+/* after */
+img { border: 0 }
+```
+
+## vendor-prefix-align
+
+Whether to align prefixes in properties and values.
+
+Acceptable value: `true`.
+
+Example: `{ "vendor-prefix-align": true }`
+
+```css
+/* before */
+a
+{
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
+ background: -webkit-linear-gradient(top, #fff 0, #eee 100%);
+ background: -moz-linear-gradient(top, #fff 0, #eee 100%);
+ background: linear-gradient(to bottom, #fff 0, #eee 100%);
+}
+
+/* after */
+a
+{
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
+ background: -webkit-linear-gradient(top, #fff 0, #eee 100%);
+ background: -moz-linear-gradient(top, #fff 0, #eee 100%);
+ background: linear-gradient(to bottom, #fff 0, #eee 100%);
+}
+```
+
+## lines-between-rulesets
+
+Number of line breaks between rulesets or @rules.
+
+Acceptable values:
+
+* `{Number}` — number of newlines;
+
+Example: `{ "lines-between-rulesets": 1}`
+
+```scss
+// Before:
+.foo {
+ @include border-radius(5px);
+ background: red;
+ .baz {
+ .test {
+ height: 50px;
+ }
+ }
+}.bar {
+ border: 1px solid red;
+ @media (min-width: 500px) {
+ width: 50px;
+ }
+}
+
+// After:
+.foo {
+ @include border-radius(5px);
+ background: red;
+
+ .baz {
+ .test {
+ height: 50px;
+ }
+ }
+}
+
+.bar {
+ border: 1px solid red;
+
+ @media (min-width: 500px) {
+ width: 50px;
+ }
+}
+```
+
+## verbose
+
+Whether to use `--verbose` option in CLI.
+
+Acceptable value: `true`.
+
+Example: `{ "verbose": true }`
+
+```bash
+csscomb ./test
+
+✓ test/integral.origin.css
+ test/integral.expect.css
+
+2 files processed
+1 file fixed
+96 ms spent
+```
diff --git a/doc/usage-cli.md b/doc/usage-cli.md
new file mode 100644
index 00000000..1f0a37fa
--- /dev/null
+++ b/doc/usage-cli.md
@@ -0,0 +1,102 @@
+# Command Line usage
+
+To run `csscomb`:
+
+```bash
+csscomb path[ path[...]]
+```
+
+`path` can be either a directory or a file:
+
+```bash
+csscomb assets/css public/styles.css
+```
+
+If you installed the package locally, use local bin file instead:
+
+```bash
+./node_modules/.bin/csscomb assets/css public/styles.css
+```
+
+## Options
+
+### help
+
+If you run `csscomb -h`, it will show some helpful information:
+
+```bash
+csscomb -h
+
+ Usage: csscomb [options]
+
+ Options:
+
+ -h, --help output usage information
+ -V, --version output the version number
+ -v, --verbose verbose mode
+ -c, --config [path] configuration file path
+ -d, --detect detect mode (would return detected options)
+ -l, --lint in case some fixes needed returns an error
+ -t, --tty-mode execution in TTY mode (useful, when running tool using external app, e.g. IDE)
+```
+
+### config
+
+If you want to use custom config instead of predefined `csscomb.json` just
+put a file named `.csscomb.json` to project's root (see [configuration
+docs](configuration.md#where-to-put-config) for more information).
+However, if for some reason you would like to use custom path, do it this way:
+
+```bash
+csscomb -c path/to/config styles.css
+```
+
+### detect
+
+If you want to generate a config file based on a template file, run:
+
+```bash
+csscomb -d example.css > .csscomb.json
+```
+
+See [configuration docs](configuration.md#generate-config-from-a-template) for
+more information.
+
+### lint
+
+CSScomb can be used as a linter, i.e. telling you what should be changed instead
+of modifying anything.
+This option should be combined with `--verbose`:
+
+```bash
+csscomb -lv assets/css
+
+ assets/css/main.scss
+! assets/css/main.scss
+! assets/css/main.scss
+! assets/css/main.scss
+
+4 files processed
+0 files fixed
+spent: 30ms
+```
+
+Exclamation mark is a sign that something is wrong with the file.
+
+### verbose
+
+If you are curious or you just like a lot of output info, `--verbose` is your
+good friend:
+
+```bash
+csscomb -v assets/css
+
+ assets/css/main.scss
+✓ assets/css/main.scss
+✓ assets/css/main.scss
+✓ assets/css/main.scss
+
+4 files processed
+3 files fixed
+spent: 33ms
+```
diff --git a/doc/usage-node.md b/doc/usage-node.md
new file mode 100644
index 00000000..5a3fbcc6
--- /dev/null
+++ b/doc/usage-node.md
@@ -0,0 +1,113 @@
+# Node.js module usage
+
+CSScomb can be used in Node.js projects: inside a plugin or as a dev tool.
+
+Workflow can look like this:
+
+```js
+// Require:
+var Comb = require('csscomb');
+var config = require('path/to/config');
+
+// Configure:
+var comb = new Comb(config);
+
+// Use:
+comb.processPath('style.css');
+```
+
+## new Comb(config)
+
+Create instance's prototype.
+
+Parameters:
+
+* `{String|Object} config` — config that should be used after creating
+ instance. Should be JSON or one of predefined config's name. Optional.
+
+Example: Create CSScomb instance and configure it using predefined `yandex` sort
+order
+
+```js
+var comb = new Comb('yandex');
+
+// This is shortcut for:
+var comb = new Comb();
+var config = Comb.getConfig('yandex');
+comb.configure(config);
+```
+
+Example: Create CSScomb instance and configure it using config object
+
+```js
+var config = require('path/to/config');
+var comb = new Comb(config);
+
+// This is shortcut for:
+var comb = new Comb();
+comb.configure(config);
+```
+
+## Comb.getConfig(name)
+
+Get one of predefined configs.
+
+Note that this is a static method.
+
+Parameters:
+
+* `{String} name` — config's name. Should be one of the following:
+ `csscomb`, `zen` or `yandex`.
+
+Example: Configure CSScomb using predefined `zen` sort order that is slightly
+modified.
+
+```js
+var config = Comb.getConfig('zen');
+config['always-semicolon'] = true;
+comb.configure(config);
+```
+
+## Comb.detectInFile(path, options)
+
+Get config options that can be detected in a file.
+
+Note that this is a static method.
+
+Parameters:
+
+* `{String} path` — path to stylesheet
+* `{Array} options` — list of options to detect. Optional. By default tries
+ to detect all available options.
+
+Example: Configure CSScomb using template file
+
+```js
+var config = comb.detectInFile('template.css');
+comb.configure(config);
+```
+
+## Comb.detectInString(string, options)
+
+Get config options that can be detected in a string.
+
+Note that this is a static method.
+
+Parameters:
+
+* `{String} string` — stylesheet
+* `{Array} options` — list of options to detect. Optional. By default tries
+ to detect all available options.
+
+Example: Configure CSScomb using template stylesheet
+
+```js
+var css = 'a {top: 0; left: 0}';
+var config = comb.detectInString(css);
+comb.configure(config);
+```
+
+## Public methods
+
+For the list of public methods and examples of their usage, see [CSScomb Core docs](https://github.com/csscomb/core).
+
diff --git a/lib/cli.js b/lib/cli.js
deleted file mode 100644
index 0afd8fe4..00000000
--- a/lib/cli.js
+++ /dev/null
@@ -1,57 +0,0 @@
-/**
- * Command line implementation for CSSComb
- *
- * Usage example:
- * ./node_modules/.bin/csscomb [options] file1 [dir1 [fileN [dirN]]]
- */
-var fs = require('fs');
-var program = require('commander');
-var vow = require('vow');
-var Comb = require('./csscomb');
-
-program
- .version(require('../package.json').version)
- .usage('[options] ')
- .option('-v, --verbose', 'verbose mode')
- .option('-c, --config [path]', 'configuration file path')
- .option('-l, --lint', 'in case some fixes needed returns an error')
- .parse(process.argv);
-
-if (!program.args.length) {
- console.log('No input paths specified');
- program.help();
-}
-
-var configPath = program.config || (process.cwd() + '/.csscomb.json');
-
-if (fs.existsSync(configPath)) {
- var comb = new Comb();
- var config = require(configPath);
-
- console.time('spent');
-
- config.verbose = program.verbose === true || config.verbose;
- config.lint = program.lint;
-
- comb.configure(config);
-
- vow.all(program.args.map(comb.processPath.bind(comb)))
- .then(function() {
- if (config.verbose) {
- console.log('');
- console.log(comb.processed + ' file' + (comb.processed === 1 ? '' : 's') + ' processed');
- console.log(comb.changed + ' file' + (comb.changed === 1 ? '' : 's') + ' fixed');
- console.timeEnd('spent');
- }
- if (config.lint && comb.tbchanged) {
- process.exit(1);
- }
- })
- .fail(function(e) {
- console.log('stack: ', e.stack);
- process.exit(1);
- });
-} else {
- console.log('Configuration file ' + configPath + ' was not found.');
- process.exit(1);
-}
diff --git a/lib/csscomb.js b/lib/csscomb.js
deleted file mode 100644
index 937b9663..00000000
--- a/lib/csscomb.js
+++ /dev/null
@@ -1,227 +0,0 @@
-var gonzales = require('gonzales');
-var minimatch = require('minimatch');
-var vow = require('vow');
-var vfs = require('vow-fs');
-var doNothing = function() {};
-
-/**
- * Starts Code Style processing process.
- *
- * @constructor
- * @name Comb
- */
-var Comb = function() {
- this._options = [
- 'remove-empty-rulesets',
- 'always-semicolon',
- 'color-case',
- 'color-shorthand',
- 'element-case',
- 'leading-zero',
- 'strip-spaces',
- 'eof-newline',
- 'stick-brace',
- 'colon-space',
- 'combinator-space',
- 'rule-indent',
- 'block-indent',
- 'unitless-zero',
- 'sort-order',
- 'vendor-prefix-align'
- ];
- this._exclude = null;
-};
-
-Comb.prototype = {
-
- /**
- * Loads configuration from JSON.
- * Activates and configures required options.
- *
- * @param {Object} config
- */
- configure: function(config) {
- this._handlers = [];
- this._options.forEach(function(option) {
- if (typeof config[option] === 'undefined') return;
-
- try {
- var handler = require('./options/' + option).setValue(config[option]);
- handler && this._handlers.push(handler);
- } catch (e) {
- console.warn('Error loading "%s" handler: %s', option, e.message);
- }
- }, this);
-
- this._exclude = (config.exclude || []).map(function(pattern) {
- return new minimatch.Minimatch(pattern);
- });
-
- this.processed = 0;
- this.tbchanged = 0;
- this.changed = 0;
- this._verbose = config.verbose;
- this._lint = config.lint;
- },
-
- /**
- * Processes stylesheet tree node.
- *
- * @param {Array} tree Parsed tree
- * @returns {Array}
- */
- processTree: function(tree) {
-
- // We walk across complete tree for each handler,
- // because we need strictly maintain order in which handlers work,
- // despite fact that handlers work on different level of the tree.
- this._handlers.forEach(function(handler) {
- this.processNode(['tree', tree], 0, handler);
- }, this);
- return tree;
- },
-
- /**
- * Processes tree node.
- * @param {Array} node Tree node
- * @param {Number} level Indent level
- */
- processNode: function(node, level, handler) {
- node.forEach(function(node) {
- if (!Array.isArray(node)) return;
-
- var nodeType = node.shift();
- handler.process(nodeType, node, level);
- node.unshift(nodeType);
-
- if (nodeType === 'atrulers') level++;
-
- this.processNode(node, level, handler);
- }, this);
- },
-
- /**
- * Process file provided with a string.
- * @param {String} text
- * @param {String} [filename]
- */
- processString: function(text, filename) {
- if (!text) return text;
- var tree;
- var string = JSON.stringify;
- try {
- tree = gonzales.srcToCSSP(text);
- } catch (e) {
- throw new Error('Parsing error at ' + filename + ': ' + e.message);
- }
- if (typeof tree === 'undefined') {
- throw new Error('Undefined tree at ' + filename + ': ' + string(text) + ' => ' + string(tree));
- }
- tree = this.processTree(tree);
- return gonzales.csspToSrc(tree);
- },
-
- /**
- * Process single file.
- *
- * @param {String} path
- * @returns {Promise}
- */
- processFile: function(path) {
- var _this = this;
- if (this._shouldProcess(path) && path.match(/\.css$/)) {
- return vfs.read(path, 'utf8').then(function(data) {
- var processedData = _this.processString(data, path);
- var changed = data !== processedData;
- var lint = _this._lint;
-
- var tick = changed ? (lint ? '!' : '✓') : ' ';
- var message = _this._verbose ? console.log.bind(null, tick, path) : doNothing;
-
- _this.processed++;
- changed && _this.tbchanged++;
-
- if (!changed || lint) {
- message();
- } else {
- return vfs.write(path, processedData, 'utf8').then(function() {
- _this.changed++;
- message();
- });
- }
- });
- }
- return null;
- },
-
- /**
- * Process directory recursively.
- *
- * @param {String} path
- * @returns {Promise}
- */
- processDirectory: function(path) {
- var _this = this;
- return vfs.listDir(path).then(function(filenames) {
- return vow.all(filenames.map(function(filename) {
- var fullname = path + '/' + filename;
- if (_this._shouldProcess(fullname)) {
- return vfs.stat(fullname).then(function(stat) {
- if (stat.isDirectory()) {
- return _this.processDirectory(fullname);
- } else if (fullname.match(/\.css$/)) {
- return vow.when(_this.processFile(fullname)).then(function(errors) {
- if (errors) return errors;
- });
- }
- });
- }
- })).then(function(results) {
- return [].concat.apply([], results);
- });
- });
- },
-
- /**
- * Process directory or file.
- *
- * @param {String} path
- */
- processPath: function(path) {
- path = path.replace(/\/$/, '');
- var _this = this;
- return vfs.exists(path).then(function(exists) {
- if (exists) {
- return vfs.stat(path).then(function(stat) {
- if (stat.isDirectory()) {
- return _this.processDirectory(path);
- } else {
- return vow.when(_this.processFile(path)).then(function(errors) {
- if (errors) return [errors];
- return;
- });
- }
- });
- } else {
- throw new Error('Path ' + path + ' was not found.');
- }
- });
- },
-
- /**
- * Returns true if specified path is not in excluded list.
- *
- * @returns {Boolean}
- */
- _shouldProcess: function(path) {
- path = path.replace(/^\.\//, '');
- var exclude = this._exclude;
- for (var i = exclude.length; i--;) {
- if (exclude[i].match(path)) return false;
- }
- return true;
- }
-
-};
-
-module.exports = Comb;
diff --git a/lib/options/always-semicolon.js b/lib/options/always-semicolon.js
deleted file mode 100644
index c0a32091..00000000
--- a/lib/options/always-semicolon.js
+++ /dev/null
@@ -1,42 +0,0 @@
-module.exports = {
-
- /**
- * Sets handler value.
- *
- * @param {String|Boolean} value Option value
- * @returns {Object|undefined}
- */
- setValue: function(value) {
- this._value = value === true;
- if (!this._value) return;
- return this;
- },
-
- /**
- * Processes tree node.
- * @param {String} nodeType
- * @param {node} node
- */
- process: function(nodeType, node) {
- if (nodeType === 'block') {
- for (var i = node.length; i--;) {
- var nodeItem = node[i];
- var type = nodeItem[0];
- var value = nodeItem[2];
-
- if (type === 'decldelim') break;
-
- if (type === 'declaration') {
- var space = [];
- for (var j = value.length; j--;) {
- if (value[j][0] !== 's' && value[j][0] !== 'comment') break;
- space.unshift(value.splice(j)[0]);
- }
- node.splice.apply(node, [i + 1, 0, ['decldelim']].concat(space));
- break;
- }
- }
- }
- }
-
-};
diff --git a/lib/options/block-indent.js b/lib/options/block-indent.js
deleted file mode 100644
index 06e2ee06..00000000
--- a/lib/options/block-indent.js
+++ /dev/null
@@ -1,74 +0,0 @@
-module.exports = {
-
- /**
- * Sets handler value.
- *
- * @param {String|Number|Boolean} value Option value
- * @returns {Object|undefined}
- */
- setValue: function(value) {
- delete this._value;
- if (value === true) this._value = ' ';
- if (typeof value === 'number' && value === Math.abs(Math.round(value)))
- this._value = new Array(value + 1).join(' ');
- if (typeof value === 'string' && value.match(/^[ \t]+$/)) this._value = value;
- if (typeof this._value === 'string') return this;
- },
-
- /**
- * Processes tree node.
- * @param {String} nodeType
- * @param {node} node
- */
- process: function(nodeType, node, level) {
- var value;
- var space;
-
- // Add right space before closing brace
- if (nodeType === 'atrulers' || nodeType === 'block') {
- value = '\n' + new Array(level + 1).join(this._value);
- if (node[node.length - 1][0] === 's') node.pop();
- node.push(['s', value]);
- }
-
- // Add right space before rule declaration
- if (nodeType === 'stylesheet' || nodeType === 'atrulers') {
- // Level up hack
- if (nodeType === 'atrulers') level++;
-
- // Prevent line break at file start
- var isFirst;
- if (nodeType === 'stylesheet') {
- var first = node[0];
- if (first[0] !== 's' || first[1].match('\n') === null) isFirst = true;
- }
-
- for (var i = 0; i < node.length; i++) {
- var nodeItem = node[i];
- if (nodeItem[0] === 'atruler' || nodeItem[0] === 'ruleset') {
- value = (i < 2 && isFirst ? '' : '\n') + new Array(level + 1).join(this._value);
- isFirst = false;
-
- space = node[i - 1];
- if (!space || space[0] !== 's') {
- var tail = node.splice(i);
- space = ['s', ''];
- tail.unshift(space);
- Array.prototype.push.apply(node, tail);
- i++;
- }
- space[1] = space[1].replace(/(\n)?([\t ]+)?$/, value);
- }
- }
- }
-
- // Add space before block opening brace
- if (nodeType === 'simpleselector') {
- space = node[node.length - 1];
- if (space[0] === 's' && space[1].match('\n')) {
- space[1] += new Array(level + 1).join(this._value);
- }
- }
- }
-
-};
diff --git a/lib/options/colon-space.js b/lib/options/colon-space.js
deleted file mode 100644
index 2abfc72f..00000000
--- a/lib/options/colon-space.js
+++ /dev/null
@@ -1,58 +0,0 @@
-module.exports = {
-
- /**
- * Sets handler value.
- *
- * @param {String|Boolean} value Option value
- * @returns {Object|undefined}
- */
- setValue: function(value) {
- this._value = false;
- if (value === true)
- value = 'after';
- if (value === false)
- value = '';
- if (value === 'both')
- this._value = [' ', ' '];
- if (value === 'before')
- this._value = [' ', ''];
- if (value === 'after')
- this._value = ['', ' '];
- if (value.constructor === Array && value[0].match(/^[ \t]*$/) && value[1].match(/^[ \t]*$/))
- this._value = value;
- if (typeof value === 'string') {
- if (value.match(/^[ \t]*$/)) {
- this._value = ['', value];
- } else {
- var detectSpaces = value.match(/^(([ \t]*):)?([ \t]*)$/);
- if (detectSpaces) {
- if (detectSpaces[1]) {
- this._value = [detectSpaces[2], detectSpaces[3]];
- } else {
- this._value = ['', detectSpaces[3]];
- }
- }
- }
- }
-
- if (!this._value) return;
- return this;
- },
-
- /**
- * Processes tree node.
- * @param {String} nodeType
- * @param {node} node
- */
- process: function(nodeType, node) {
- if (nodeType === 'property') {
- if (node[node.length - 1][0] === 's') node.pop();
- if (this._value[0] !== '') node.push(['s', this._value[0]]);
- }
- if (nodeType === 'value') {
- if (node[0][0] === 's') node.shift();
- if (this._value[1] !== '') node.unshift(['s', this._value[1]]);
- }
- }
-
-};
diff --git a/lib/options/color-case.js b/lib/options/color-case.js
deleted file mode 100644
index e1bf5480..00000000
--- a/lib/options/color-case.js
+++ /dev/null
@@ -1,31 +0,0 @@
-module.exports = {
-
- /**
- * Sets handler value.
- *
- * @param {String} value Option value
- * @returns {Object|undefined}
- */
- setValue: function(value) {
- if (value === 'lower' || value === 'upper') {
- this._value = value;
- return this;
- }
- },
-
- /**
- * Processes tree node.
- * @param {String} nodeType
- * @param {node} node
- */
- process: function(nodeType, node) {
- if (nodeType === 'vhash') {
- if (this._value === 'lower') {
- node[0] = node[0].toLowerCase();
- } else if (this._value === 'upper') {
- node[0] = node[0].toUpperCase();
- }
- }
- }
-
-};
diff --git a/lib/options/color-shorthand.js b/lib/options/color-shorthand.js
deleted file mode 100644
index 39954053..00000000
--- a/lib/options/color-shorthand.js
+++ /dev/null
@@ -1,31 +0,0 @@
-module.exports = {
-
- /**
- * Sets handler value.
- *
- * @param {Boolean} value Option value
- * @returns {Object|undefined}
- */
- setValue: function(value) {
- if (value === true || value === false) {
- this._value = value;
- return this;
- }
- },
-
- /**
- * Processes tree node.
- * @param {String} nodeType
- * @param {node} node
- */
- process: function(nodeType, node) {
- if (nodeType === 'vhash') {
- if (this._value) {
- node[0] = node[0].replace(/(\w)\1(\w)\2(\w)\3/i, '$1$2$3');
- } else {
- node[0] = node[0].replace(/^(\w)(\w)(\w)$/, '$1$1$2$2$3$3');
- }
- }
- }
-
-};
diff --git a/lib/options/combinator-space.js b/lib/options/combinator-space.js
deleted file mode 100644
index 3a51f2d3..00000000
--- a/lib/options/combinator-space.js
+++ /dev/null
@@ -1,50 +0,0 @@
-module.exports = {
-
- /**
- * Sets handler value.
- *
- * @param {String|Boolean|Array} value Option value
- * @returns {Object}
- */
- setValue: function(value) {
- this._value = false;
- if (value === true) value = ' ';
- if (value === false) value = '';
- if (typeof value === 'string' && value.match(/^[ \t\n]*$/)) {
- this._value = [value, value];
- }
- if (value.constructor === Array) this._value = value;
- if (!this._value) return;
- return this;
- },
-
- /**
- * Processes tree node.
- * @param {String} nodeType
- * @param {node} node
- */
- process: function(nodeType, node) {
- if (nodeType === 'selector') {
- for (var i = node.length; i--;) {
- var subSelector = node[i];
- for (var j = subSelector.length; j--;) {
- if (subSelector[j][0] === 'combinator') {
- // Working with the whitespace after the combinator
- if (subSelector[j + 1][0] === 's') {
- subSelector[j + 1][1] = this._value[1];
- } else {
- subSelector.splice(j + 1, 0, ['s', this._value[1]]);
- }
- // Working with the whitespace before the combinator
- if (subSelector[j - 1][0] === 's') {
- subSelector[j - 1][1] = this._value[0];
- } else {
- subSelector.splice(j, 0, ['s', this._value[0]]);
- }
- }
- }
- }
- }
- }
-
-};
diff --git a/lib/options/element-case.js b/lib/options/element-case.js
deleted file mode 100644
index a0acca70..00000000
--- a/lib/options/element-case.js
+++ /dev/null
@@ -1,36 +0,0 @@
-module.exports = {
-
- /**
- * Sets handler value.
- *
- * @param {String} value Option value
- * @returns {Object|undefined}
- */
- setValue: function(value) {
- if (value === 'lower' || value === 'upper') {
- this._value = value;
- return this;
- }
- },
-
- /**
- * Processes tree node.
- * @param {String} nodeType
- * @param {node} node
- */
- process: function(nodeType, node) {
- if (nodeType === 'simpleselector') {
- for (var i = node.length; i--;) {
- var nodeItem = node[i];
- if (nodeItem[0] === 'ident') {
- if (this._value === 'lower') {
- nodeItem[1] = nodeItem[1].toLowerCase();
- } else if (this._value === 'upper') {
- nodeItem[1] = nodeItem[1].toUpperCase();
- }
- }
- }
- }
- }
-
-};
diff --git a/lib/options/eof-newline.js b/lib/options/eof-newline.js
deleted file mode 100644
index e0802228..00000000
--- a/lib/options/eof-newline.js
+++ /dev/null
@@ -1,33 +0,0 @@
-module.exports = {
-
- /**
- * Sets handler value.
- *
- * @param {Boolean} value Option value
- * @returns {Object|undefined}
- */
- setValue: function(value) {
- if (value === true || value === false) {
- this._value = value;
- return this;
- }
- },
-
- /**
- * Processes tree node.
- * @param {String} nodeType
- * @param {node} node
- */
- process: function(nodeType, node) {
- if (nodeType === 'stylesheet') {
- var lastChild = node[node.length - 1];
- if (lastChild[0] !== 's') {
- lastChild = ['s', ''];
- node.push(lastChild);
- }
- lastChild[1] = lastChild[1].replace(/\n$/, '');
- if (this._value) lastChild[1] += '\n';
- }
- }
-
-};
diff --git a/lib/options/leading-zero.js b/lib/options/leading-zero.js
deleted file mode 100644
index cb105845..00000000
--- a/lib/options/leading-zero.js
+++ /dev/null
@@ -1,32 +0,0 @@
-module.exports = {
-
- /**
- * Sets handler value.
- *
- * @param {Boolean} value Option value
- * @returns {Object|undefined}
- */
- setValue: function(value) {
- if (value === true || value === false) {
- this._value = value;
- return this;
- }
- },
-
- /**
- * Processes tree node.
- * @param {String} nodeType
- * @param {node} node
- */
- process: function(nodeType, node) {
- if (nodeType === 'number') {
- if (this._value) {
- if (node[0][0] === '.')
- node[0] = '0' + node[0];
- } else {
- node[0] = node[0].replace(/^0+(?=\.)/, '');
- }
- }
- }
-
-};
diff --git a/lib/options/remove-empty-rulesets.js b/lib/options/remove-empty-rulesets.js
deleted file mode 100644
index 10c818b5..00000000
--- a/lib/options/remove-empty-rulesets.js
+++ /dev/null
@@ -1,85 +0,0 @@
-module.exports = {
-
- /**
- * Sets handler value.
- *
- * @param {String} value Option value
- * @returns {Object|undefined}
- */
- setValue: function(value) {
- if (value === true) {
- this._value = value;
- return this;
- }
- },
-
- /**
- * Remove rulesets with no declarations.
- *
- * @param {String} nodeType
- * @param {Array} nodeContent
- */
- process: function(nodeType, nodeContent) {
- if (nodeType === 'stylesheet') {
- this._processStylesheetContent(nodeContent);
- }
- },
-
- _processStylesheetContent: function(nodeContent) {
- this._removeEmptyRulesets(nodeContent);
- this._mergeAdjacentWhitespace(nodeContent);
- },
-
- _removeEmptyRulesets: function(nodeContent) {
- var i = nodeContent.length;
- while (i--) {
- if (this._isRuleset(nodeContent[i]) && this._isEmptyRuleset(nodeContent[i])) {
- nodeContent.splice(i, 1);
- }
- }
- },
-
- /**
- * Removing ruleset nodes from tree may result in two adjacent whitespace nodes which is not correct AST:
- * [space, ruleset, space] => [space, space]
- * To ensure correctness of further processing we should merge such nodes into one.
- * [space, space] => [space]
- */
- _mergeAdjacentWhitespace: function(nodeContent) {
- var i = nodeContent.length - 1;
- while (i-- > 0) {
- if (this._isWhitespace(nodeContent[i]) && this._isWhitespace(nodeContent[i + 1])) {
- nodeContent[i][1] += nodeContent[i + 1][1];
- nodeContent.splice(i + 1, 1);
- }
- }
- },
-
- _isEmptyRuleset: function(ruleset) {
- return ruleset.filter(this._isBlock).every(this._isEmptyBlock, this);
- },
-
- /**
- * Block considered empty when it has no declarations or comments.
- */
- _isEmptyBlock: function(node) {
- return !node.some(this._isDeclarationOrComment);
- },
-
- _isDeclarationOrComment: function(node) {
- return node[0] === 'declaration' || node[0] === 'comment';
- },
-
- _isRuleset: function(node) {
- return node[0] === 'ruleset';
- },
-
- _isBlock: function(node) {
- return node[0] === 'block';
- },
-
- _isWhitespace: function(node) {
- return node[0] === 's';
- }
-
-};
diff --git a/lib/options/rule-indent.js b/lib/options/rule-indent.js
deleted file mode 100644
index 5fb76171..00000000
--- a/lib/options/rule-indent.js
+++ /dev/null
@@ -1,48 +0,0 @@
-module.exports = {
-
- /**
- * Sets handler value.
- *
- * @param {String|Number|Boolean} value Option value
- * @returns {Object|undefined}
- */
- setValue: function(value) {
- delete this._value;
- if (value === true) this._value = ' ';
- if (typeof value === 'number' && value === Math.abs(Math.round(value)))
- this._value = new Array(value + 1).join(' ');
- if (typeof value === 'string' && value.match(/^[ \t]+$/)) this._value = value;
- if (typeof this._value === 'string') return this;
- },
-
- /**
- * Processes tree node.
- * @param {String} nodeType
- * @param {node} node
- */
- process: function(nodeType, node, level) {
- if (nodeType === 'block') {
- if (node[0][0] !== 's') {
- node.unshift(['s', '']);
- }
- for (var i = 0; i < node.length; i++) {
- var nodeItem = node[i];
- if (nodeItem[0] === 'declaration') {
- var value = '\n' + new Array(level + 2).join(this._value);
- var space = node[i - 1];
- var tail;
-
- if (space[0] !== 's') {
- space = ['s', ''];
- tail = node.splice(i);
- tail.unshift(space);
- Array.prototype.push.apply(node, tail);
- i++;
- }
- space[1] = space[1].replace(/(\n)?([\t ]+)?$/, value);
- }
- }
- }
- }
-
-};
diff --git a/lib/options/sort-order.js b/lib/options/sort-order.js
deleted file mode 100644
index 45270142..00000000
--- a/lib/options/sort-order.js
+++ /dev/null
@@ -1,150 +0,0 @@
-module.exports = {
-
- /**
- * Sets handler value.
- *
- * @param {Array} value Option value
- * @returns {Object|undefined}
- */
- setValue: function(value) {
- if (!value) return;
-
- this._order = {};
- value.forEach(function(group, groupIndex) {
- group.forEach(function(prop, propIndex) {
- this._order[prop] = { group: groupIndex, prop: propIndex };
- }, this);
- }, this);
-
- return this;
- },
-
- /**
- * Processes tree node.
- * @param {String} nodeType
- * @param {node} node
- */
- process: function(nodeType, node) {
- if (nodeType !== 'block') return;
-
- var order = this._order;
- var nodeExt = this._createNodeExt(node);
- var firstSymbols = nodeExt[0].decl || nodeExt.shift();
- var lastSymbols = nodeExt[nodeExt.length - 1].decl || nodeExt.pop();
-
- nodeExt.sort(function(a, b) {
- var indexA = order[a.decl] || { prop: -1 };
- var indexB = order[b.decl] || { prop: -1 };
- var groupDelta = indexA.group - indexB.group;
- var propDelta = indexA.prop - indexB.prop;
-
- return groupDelta !== 0 ? groupDelta : propDelta;
- });
-
- firstSymbols && nodeExt.unshift(firstSymbols);
- lastSymbols && nodeExt.push(lastSymbols);
-
- this._applyNodeExt(node, nodeExt);
- },
-
- /**
- * Internal. Smart split bunch on '\n' symbols;
- * @param {Array} bunch
- * @param {Boolean} isPrevDeclExists
- */
- _splitBunch: function(bunch, isPrevDeclExists) {
- var nextBunch = [];
- var declAlready = false;
- var flag = false;
- var item;
- var indexOf;
-
- for (var i = 0; i < bunch.length; ++i) {
- if (flag) { nextBunch.push(bunch[i]); continue; }
- if (bunch[i][0] === 'declaration') { declAlready = true; }
-
- if (isPrevDeclExists && !declAlready) continue;
- if (bunch[i][0] !== 's') continue;
-
- var indexOfNewLine = bunch[i][1].indexOf('\n');
-
- if (indexOfNewLine === -1) continue;
-
- nextBunch.push(['s', bunch[i][1].substr(indexOfNewLine + 1)]);
- bunch[i][1] = bunch[i][1].substr(0, indexOfNewLine + 1);
-
- flag = i + 1;
- }
-
- if (nextBunch.length === 1 && nextBunch[0][0] === 's') {
- item = nextBunch[0];
- indexOf = item[1].lastIndexOf('\n');
-
- indexOf !== -1 && (item[1] = item[1].substr(indexOf + 1));
- }
-
- flag && bunch.splice(flag);
-
- return nextBunch;
- },
-
- /**
- * Internal. Create extended node in format of list
- * {
- * data:[,,declaration,]
- * decl: declarationPropertyName
- * };
- * @param {node} node
- */
- _createNodeExt: function(node) {
- var extNode = [];
- var bunch = [];
- var nextBunch;
- var prevDeclPropertyName;
-
- node.forEach(function(node) {
- if (node[0] === 'declaration') {
- nextBunch = this._splitBunch(bunch, prevDeclPropertyName);
- extNode.push({ data: bunch, decl: prevDeclPropertyName });
- bunch = nextBunch;
- prevDeclPropertyName = node[1][1][1];
- }
- bunch.push(node);
- }, this);
-
- nextBunch = this._splitBunch(bunch, prevDeclPropertyName);
- extNode.push({ data: bunch, decl: prevDeclPropertyName });
- nextBunch.length && extNode.push({ data: nextBunch });
-
- return extNode;
- },
-
- /**
- * Internal. Add delimiter at the end of groups of properties
- * @param {extNode} extNodePrev
- * @param {extNode} extNode
- */
- _addGroupDelimiter: function(extNodePrev, extNode) {
- if (!extNodePrev.decl || !extNode.decl) return;
-
- var indexA = this._order[extNodePrev.decl];
- var indexB = this._order[extNode.decl];
- var isGroupBorder = indexA && indexB && indexA.group !== indexB.group;
-
- if (isGroupBorder) extNode.data.unshift(['s', '\n']);
- },
-
- /**
- * Internal. apply extNode back at common format node.
- * @param {node} node
- * @param {extNode} extNode
- */
- _applyNodeExt: function(node, extNode) {
- node.splice(0, node.length);
- extNode.forEach(function(item, i) {
- i > 0 && this._addGroupDelimiter(extNode[i - 1], item);
- node.push.apply(node, item.data);
- }, this);
- }
-
-};
diff --git a/lib/options/stick-brace.js b/lib/options/stick-brace.js
deleted file mode 100644
index f1d4d77c..00000000
--- a/lib/options/stick-brace.js
+++ /dev/null
@@ -1,36 +0,0 @@
-module.exports = {
-
- /**
- * Sets handler value.
- *
- * @param {String|Number|Boolean} value Option value
- * @returns {Object|undefined}
- */
- setValue: function(value) {
- delete this._value;
- if (value === true) this._value = ' ';
- if (typeof value === 'number' && value === Math.abs(Math.round(value)))
- this._value = new Array(value + 1).join(' ');
- if (typeof value === 'string' && value.match(/^[ \t\n]+$/)) this._value = value;
- if (typeof this._value === 'string') return this;
- },
-
- /**
- * Processes tree node.
- * @param {String} nodeType
- * @param {node} node
- */
- process: function(nodeType, node) {
- if (nodeType === 'selector' || nodeType === 'atruler') {
- for (var i = node.length; i--;) {
- var nodeItem = node[i];
- if (nodeItem[0] === 'simpleselector' || nodeItem[0] === 'atrulerq') {
- if (nodeItem[nodeItem.length - 1][0] === 's') nodeItem.pop();
- nodeItem.push(['s', this._value]);
- break;
- }
- }
- }
- }
-
-};
diff --git a/lib/options/strip-spaces.js b/lib/options/strip-spaces.js
deleted file mode 100644
index 1b6743c4..00000000
--- a/lib/options/strip-spaces.js
+++ /dev/null
@@ -1,44 +0,0 @@
-module.exports = {
-
- /**
- * Sets handler value.
- *
- * @param {String|Boolean} value Option value
- * @returns {Object|undefined}
- */
- setValue: function(value) {
- this._value = value === true;
- if (!this._value) return;
- return this;
- },
-
- /**
- * Processes tree node.
- * @param {String} nodeType
- * @param {node} node
- */
- process: function(nodeType, node) {
- if (nodeType === 's') {
- node[0] = this._trim(node[0]);
- }
- if (nodeType === 'stylesheet') {
- var lastChild = node[node.length - 1];
- if (lastChild[0] === 's') {
- lastChild[1] = this._trim(lastChild[1])
- .replace(/[ \t]+$/, '')
- .replace(/[\n]+/g, '\n');
- }
- }
- },
-
- /**
- * Trim trailing spaces on each line.
- * @private
- * @param {String} s Spaceful string
- * @returns {String}
- */
- _trim: function(s) {
- return s.replace(/[ \t]+\n/g, '\n');
- }
-
-};
diff --git a/lib/options/unitless-zero.js b/lib/options/unitless-zero.js
deleted file mode 100644
index a4b48d9e..00000000
--- a/lib/options/unitless-zero.js
+++ /dev/null
@@ -1,34 +0,0 @@
-module.exports = {
-
- /**
- * Sets handler value.
- *
- * @param {Boolean} value Option value
- * @returns {Object|undefined}
- */
- setValue: function(value) {
- if (value === true) {
- this._value = value;
- return this;
- }
- },
-
- /**
- * Processes tree node.
- * @param {String} nodeType
- * @param {node} node
- */
- process: function(nodeType, node) {
- if (nodeType === 'value' || nodeType === 'braces') {
- node.forEach(function(child, index) {
- if (
- (child[0] === 'percentage' ||
- child[0] === 'dimension' && ['cm', 'em', 'ex', 'pt', 'px'].indexOf(child[2][1]) !== -1) &&
- child[1][1] === '0') {
- node[index] = child[1];
- }
- });
- }
- }
-
-};
diff --git a/lib/options/vendor-prefix-align.js b/lib/options/vendor-prefix-align.js
deleted file mode 100644
index eb087b82..00000000
--- a/lib/options/vendor-prefix-align.js
+++ /dev/null
@@ -1,159 +0,0 @@
-module.exports = {
-
- /**
- * Internal
- *
- * Containt vendor-prefixes list.
- */
- _prefixesList: [
- 'webkit',
- 'moz',
- 'ms',
- 'o'
- ],
-
- /**
- * Internal
- *
- * Create object which contains info about vendor prefix used in propertyName.
- * @param {String} propertyName property name
- * @returns {Object|undefined}
- */
- _getPrefixInfo: function(propertyName) {
- if (!propertyName) return;
-
- var result = { baseName: propertyName, prefixLength: 0 };
-
- this._prefixesList.some(function(prefix) {
- prefix = '-' + prefix + '-';
- if (propertyName.indexOf(prefix) !== 0) return;
- result = {
- baseName: propertyName.substr(prefix.length),
- prefixLength: prefix.length
- };
- return true;
- });
-
- return result;
- },
-
- /**
- * Internal
- *
- * Walk across nodes, and call payload for every node that pass selector check.
- * @param {node} node
- * @param {function} selector
- * @param {function} payload
- */
- _walk: function(node, selector, payload) {
- node.forEach(function(item, i) {
- var info = this._getPrefixInfo(selector(item));
- if (!info) return;
- payload(info, i);
- }, this);
- },
-
- /**
- * Internal
- *
- * Selector for property name.
- * @param {node} item
- * @returns {String|false|undefined}
- */
- _declName: function(item) {
- return item[0] === 'declaration' && item[1][1][1];
- },
-
- /**
- * Internal
- *
- * Selector for value name.
- * @param {node} item
- * @returns {String|false|undefined}
- */
- _valName: function(item) {
- return item[0] === 'declaration' && item[2] && item[2][2] &&
- item[2][2][0] === 'funktion' && item[2][2][1][0] === 'ident' &&
- item[2][2][1][1];
- },
-
- /**
- * Internal
- *
- * Update dict which contains info about items align.
- * @param {Object} info,
- * @param {Object} dict,
- * @param {String} whitespaceNode
- */
- _updateDict: function(info, dict, whitespaceNode) {
- if (info.prefixLength === 0) return;
-
- var indent = dict[info.baseName] || { prefixLength: 0, baseLength: 0 };
-
- dict[info.baseName] = indent.prefixLength > info.prefixLength ?
- indent :
- {
- prefixLength: info.prefixLength,
- baseLength: whitespaceNode.substr(whitespaceNode.lastIndexOf('\n') + 1).length
- };
- },
-
- /**
- * Return string with correct number of spaces for info.baseName property.
- * @param {Object} info,
- * @param {Object} dict,
- * @param {String} whitespaceNode
- * @returns {String}
- */
- _updateIndent: function(info, dict, whitespaceNode) {
- if (!dict[info.baseName])
- return whitespaceNode;
-
- var firstPart = whitespaceNode.substr(0, whitespaceNode.lastIndexOf('\n') + 1 );
- var extraIndent = new Array(
- dict[info.baseName].prefixLength -
- info.prefixLength +
- dict[info.baseName].baseLength + 1).join(' ');
-
- return firstPart.concat(extraIndent);
- },
-
- /**
- * Sets handler value.
- *
- * @param {Array} value Option value
- * @returns {Object|undefined}
- */
- setValue: function(value) {
- return value ? this : undefined;
- },
-
- /**
- * Processes tree node.
- * @param {String} nodeType
- * @param {node} node
- */
- process: function(nodeType, node) {
- if (nodeType !== 'block') return;
-
- var dict = {};
- var _this = this;
-
- // Gathering Info
- this._walk(node, this._declName, function(info, i) {
- _this._updateDict(info, dict, node[i - 1][1]);
- });
- this._walk(node, this._valName, function(info, i) {
- _this._updateDict(info, dict, node[i][2][1][1]);
- });
-
- // Update nodes
- this._walk(node, this._declName, function(info, i) {
- node[i - 1][1] = _this._updateIndent(info, dict, node[i - 1][1]);
- });
- this._walk(node, this._valName, function(info, i) {
- node[i][2][1][1] = _this._updateIndent(info, dict, node[i][2][1][1]);
- });
- }
-
-};
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 00000000..c7f2c458
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,4086 @@
+{
+ "name": "csscomb",
+ "version": "4.2.0",
+ "lockfileVersion": 1,
+ "requires": true,
+ "dependencies": {
+ "@babel/cli": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.2.3.tgz",
+ "integrity": "sha1-GyYuQqPpWdKKs9IFuicY4ZI8/uY=",
+ "dev": true,
+ "requires": {
+ "chokidar": "^2.0.3",
+ "commander": "^2.8.1",
+ "convert-source-map": "^1.1.0",
+ "fs-readdir-recursive": "^1.1.0",
+ "glob": "^7.0.0",
+ "lodash": "^4.17.10",
+ "mkdirp": "^0.5.1",
+ "output-file-sync": "^2.0.0",
+ "slash": "^2.0.0",
+ "source-map": "^0.5.0"
+ }
+ },
+ "@babel/code-frame": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz",
+ "integrity": "sha1-BuKrGb21NThVWaq7W6WXKUgoAPg=",
+ "dev": true,
+ "requires": {
+ "@babel/highlight": "^7.0.0"
+ }
+ },
+ "@babel/core": {
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.4.0.tgz",
+ "integrity": "sha1-JI/Wh0t9dVAQv+YfVXRh1PRG2ek=",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.0.0",
+ "@babel/generator": "^7.4.0",
+ "@babel/helpers": "^7.4.0",
+ "@babel/parser": "^7.4.0",
+ "@babel/template": "^7.4.0",
+ "@babel/traverse": "^7.4.0",
+ "@babel/types": "^7.4.0",
+ "convert-source-map": "^1.1.0",
+ "debug": "^4.1.0",
+ "json5": "^2.1.0",
+ "lodash": "^4.17.11",
+ "resolve": "^1.3.2",
+ "semver": "^5.4.1",
+ "source-map": "^0.5.0"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+ "integrity": "sha1-O3ImAlUQnGtYnO4FDx1RYTlmR5E=",
+ "dev": true,
+ "requires": {
+ "ms": "^2.1.1"
+ }
+ },
+ "ms": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
+ "integrity": "sha1-MKWGTrPrsKZvLr5tcnrwagnYbgo=",
+ "dev": true
+ }
+ }
+ },
+ "@babel/generator": {
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.4.0.tgz",
+ "integrity": "sha1-wjDnlYmuenKf1GMbne1NwiBBgZY=",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.4.0",
+ "jsesc": "^2.5.1",
+ "lodash": "^4.17.11",
+ "source-map": "^0.5.0",
+ "trim-right": "^1.0.1"
+ }
+ },
+ "@babel/helper-function-name": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz",
+ "integrity": "sha1-oM6wFoX3M1XUNgwSR/WCv6/I/1M=",
+ "dev": true,
+ "requires": {
+ "@babel/helper-get-function-arity": "^7.0.0",
+ "@babel/template": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "@babel/helper-get-function-arity": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz",
+ "integrity": "sha1-g1ctQyDipGVyY3NBE8QoaLZOScM=",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "@babel/helper-plugin-utils": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz",
+ "integrity": "sha1-u7P77phmHFaQNCN8wDlnupm08lA=",
+ "dev": true
+ },
+ "@babel/helper-split-export-declaration": {
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.0.tgz",
+ "integrity": "sha1-Vxv9UnAfSSkg1jt/c1Aw6aPhC1U=",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.4.0"
+ }
+ },
+ "@babel/helpers": {
+ "version": "7.4.2",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.4.2.tgz",
+ "integrity": "sha1-O9+kalUsp371oPhVG+XwhFrpib4=",
+ "dev": true,
+ "requires": {
+ "@babel/template": "^7.4.0",
+ "@babel/traverse": "^7.4.0",
+ "@babel/types": "^7.4.0"
+ }
+ },
+ "@babel/highlight": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz",
+ "integrity": "sha1-9xDDjI1Fjm3ZogGvtjf8t4HOmeQ=",
+ "dev": true,
+ "requires": {
+ "chalk": "^2.0.0",
+ "esutils": "^2.0.2",
+ "js-tokens": "^4.0.0"
+ }
+ },
+ "@babel/node": {
+ "version": "7.2.2",
+ "resolved": "https://registry.npmjs.org/@babel/node/-/node-7.2.2.tgz",
+ "integrity": "sha1-FVfdI1RbONex0DCpwOj7Il2/cKs=",
+ "dev": true,
+ "requires": {
+ "@babel/polyfill": "^7.0.0",
+ "@babel/register": "^7.0.0",
+ "commander": "^2.8.1",
+ "lodash": "^4.17.10",
+ "v8flags": "^3.1.1"
+ }
+ },
+ "@babel/parser": {
+ "version": "7.4.2",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.4.2.tgz",
+ "integrity": "sha1-tFIaQAy1qHHqs4kHh7S8EybTjZE=",
+ "dev": true
+ },
+ "@babel/plugin-transform-destructuring": {
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.4.0.tgz",
+ "integrity": "sha1-rLubJBjSkBB9szP01s2Kpq6gA0M=",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.0.0"
+ }
+ },
+ "@babel/plugin-transform-strict-mode": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-strict-mode/-/plugin-transform-strict-mode-7.2.0.tgz",
+ "integrity": "sha1-Gv9Lmc6Ye0cVJWaph+lGnke5ZiY=",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.0.0"
+ }
+ },
+ "@babel/polyfill": {
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/@babel/polyfill/-/polyfill-7.4.0.tgz",
+ "integrity": "sha1-kPnWiuNKxCq0tKoDFRhI9TaWAhg=",
+ "requires": {
+ "core-js": "^2.6.5",
+ "regenerator-runtime": "^0.13.2"
+ }
+ },
+ "@babel/register": {
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.4.0.tgz",
+ "integrity": "sha1-2dCmIdsmj7FCAPJoWk+JJMgiQEw=",
+ "dev": true,
+ "requires": {
+ "core-js": "^3.0.0",
+ "find-cache-dir": "^2.0.0",
+ "lodash": "^4.17.11",
+ "mkdirp": "^0.5.1",
+ "pirates": "^4.0.0",
+ "source-map-support": "^0.5.9"
+ },
+ "dependencies": {
+ "core-js": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.0.0.tgz",
+ "integrity": "sha1-qNv6l40pv8Jjv7ZsVW0MqSTCiVc=",
+ "dev": true
+ }
+ }
+ },
+ "@babel/template": {
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.4.0.tgz",
+ "integrity": "sha1-EkdOnAd7rlhcXYNalcCwt5DCXIs=",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.0.0",
+ "@babel/parser": "^7.4.0",
+ "@babel/types": "^7.4.0"
+ }
+ },
+ "@babel/traverse": {
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.4.0.tgz",
+ "integrity": "sha1-FABpZ90dKzSUzdZQxobbna8N2to=",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.0.0",
+ "@babel/generator": "^7.4.0",
+ "@babel/helper-function-name": "^7.1.0",
+ "@babel/helper-split-export-declaration": "^7.4.0",
+ "@babel/parser": "^7.4.0",
+ "@babel/types": "^7.4.0",
+ "debug": "^4.1.0",
+ "globals": "^11.1.0",
+ "lodash": "^4.17.11"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+ "integrity": "sha1-O3ImAlUQnGtYnO4FDx1RYTlmR5E=",
+ "dev": true,
+ "requires": {
+ "ms": "^2.1.1"
+ }
+ },
+ "ms": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
+ "integrity": "sha1-MKWGTrPrsKZvLr5tcnrwagnYbgo=",
+ "dev": true
+ }
+ }
+ },
+ "@babel/types": {
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.4.0.tgz",
+ "integrity": "sha1-Zwck930kzObMfYz2RZnVEdFkiUw=",
+ "dev": true,
+ "requires": {
+ "esutils": "^2.0.2",
+ "lodash": "^4.17.11",
+ "to-fast-properties": "^2.0.0"
+ }
+ },
+ "acorn": {
+ "version": "6.1.1",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.1.tgz",
+ "integrity": "sha1-fSWuBbuK0fm2mRCOEJTs14hK3B8=",
+ "dev": true
+ },
+ "acorn-jsx": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.0.1.tgz",
+ "integrity": "sha1-MqBk/ZJUKSFqCbFBECv90YX65A4=",
+ "dev": true
+ },
+ "ajv": {
+ "version": "6.10.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz",
+ "integrity": "sha1-kNDVRDnaWHzX6EO/twRfUL0ivfE=",
+ "dev": true,
+ "requires": {
+ "fast-deep-equal": "^2.0.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ }
+ },
+ "ansi-colors": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz",
+ "integrity": "sha1-V9NbhoboUeLMBMQD8cACA5dqGBM=",
+ "dev": true
+ },
+ "ansi-escapes": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz",
+ "integrity": "sha1-h4C5j/nb9WOBUtHx/lwde0RCl2s=",
+ "dev": true
+ },
+ "ansi-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
+ "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
+ "dev": true
+ },
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha1-QfuyAkPlCxK+DwS43tvwdSDOhB0=",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "anymatch": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz",
+ "integrity": "sha1-vLJLTzeTTZqnrBe0ra+J58du8us=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "micromatch": "^3.1.4",
+ "normalize-path": "^2.1.1"
+ },
+ "dependencies": {
+ "normalize-path": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz",
+ "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "remove-trailing-separator": "^1.0.1"
+ }
+ }
+ }
+ },
+ "argparse": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha1-vNZ5HqWuCXJeF+WtmIE0zUCz2RE=",
+ "dev": true,
+ "requires": {
+ "sprintf-js": "~1.0.2"
+ }
+ },
+ "arr-diff": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
+ "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=",
+ "dev": true
+ },
+ "arr-flatten": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz",
+ "integrity": "sha1-NgSLv/TntH4TZkQxbJlmnqWukfE=",
+ "dev": true
+ },
+ "arr-union": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz",
+ "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=",
+ "dev": true
+ },
+ "array-unique": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
+ "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=",
+ "dev": true
+ },
+ "assign-symbols": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz",
+ "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=",
+ "dev": true
+ },
+ "astral-regex": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz",
+ "integrity": "sha1-bIw/uCfdQ+45GPJ7gngqt2WKb9k=",
+ "dev": true
+ },
+ "async-each": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.2.tgz",
+ "integrity": "sha1-i4p8oqZY+Sfp8wfW0aQvQZnw9zU=",
+ "dev": true,
+ "optional": true
+ },
+ "atob": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
+ "integrity": "sha1-bZUX654DDSQ2ZmZR6GvZ9vE1M8k=",
+ "dev": true
+ },
+ "balanced-match": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
+ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
+ },
+ "base": {
+ "version": "0.11.2",
+ "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz",
+ "integrity": "sha1-e95c7RRbbVUakNuH+DxVi060io8=",
+ "dev": true,
+ "requires": {
+ "cache-base": "^1.0.1",
+ "class-utils": "^0.3.5",
+ "component-emitter": "^1.2.1",
+ "define-property": "^1.0.0",
+ "isobject": "^3.0.1",
+ "mixin-deep": "^1.2.0",
+ "pascalcase": "^0.1.1"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
+ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^1.0.0"
+ }
+ },
+ "is-accessor-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+ "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-data-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+ "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-descriptor": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+ "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=",
+ "dev": true,
+ "requires": {
+ "is-accessor-descriptor": "^1.0.0",
+ "is-data-descriptor": "^1.0.0",
+ "kind-of": "^6.0.2"
+ }
+ }
+ }
+ },
+ "binary-extensions": {
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.0.tgz",
+ "integrity": "sha1-lSPgATBqMkRLkHQj8d4hZCIvarE=",
+ "dev": true,
+ "optional": true
+ },
+ "brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha1-PH/L9SnYcibz0vUrlm/1Jx60Qd0=",
+ "requires": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "braces": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
+ "integrity": "sha1-WXn9PxTNUxVl5fot8av/8d+u5yk=",
+ "dev": true,
+ "requires": {
+ "arr-flatten": "^1.1.0",
+ "array-unique": "^0.3.2",
+ "extend-shallow": "^2.0.1",
+ "fill-range": "^4.0.0",
+ "isobject": "^3.0.1",
+ "repeat-element": "^1.1.2",
+ "snapdragon": "^0.8.1",
+ "snapdragon-node": "^2.0.1",
+ "split-string": "^3.0.2",
+ "to-regex": "^3.0.1"
+ },
+ "dependencies": {
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "dev": true,
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ }
+ }
+ },
+ "browser-stdout": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz",
+ "integrity": "sha1-uqVZ7hTO1zRSIputcyZGfGH6vWA=",
+ "dev": true
+ },
+ "buffer-from": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
+ "integrity": "sha1-MnE7wCj3XAL9txDXx7zsHyxgcO8=",
+ "dev": true
+ },
+ "cache-base": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz",
+ "integrity": "sha1-Cn9GQWgxyLZi7jb+TnxZ129marI=",
+ "dev": true,
+ "requires": {
+ "collection-visit": "^1.0.0",
+ "component-emitter": "^1.2.1",
+ "get-value": "^2.0.6",
+ "has-value": "^1.0.0",
+ "isobject": "^3.0.1",
+ "set-value": "^2.0.0",
+ "to-object-path": "^0.3.0",
+ "union-value": "^1.0.0",
+ "unset-value": "^1.0.0"
+ }
+ },
+ "callsites": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.0.0.tgz",
+ "integrity": "sha1-+361abcq16RYEvk/2UMKPkELPdM=",
+ "dev": true
+ },
+ "camelcase": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.2.0.tgz",
+ "integrity": "sha1-51IqvaXtlMwEieG4RmYQ6IQEz0U=",
+ "dev": true
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha1-zUJUFnelQzPPVBpJEIwUMrRMlCQ=",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "chardet": {
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
+ "integrity": "sha1-kAlISfCTfy7twkJdDSip5fDLrZ4=",
+ "dev": true
+ },
+ "chokidar": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.5.tgz",
+ "integrity": "sha1-CuhDTZYigaX1bHKGnnnLbZ2GrU0=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "anymatch": "^2.0.0",
+ "async-each": "^1.0.1",
+ "braces": "^2.3.2",
+ "fsevents": "^1.2.7",
+ "glob-parent": "^3.1.0",
+ "inherits": "^2.0.3",
+ "is-binary-path": "^1.0.0",
+ "is-glob": "^4.0.0",
+ "normalize-path": "^3.0.0",
+ "path-is-absolute": "^1.0.0",
+ "readdirp": "^2.2.1",
+ "upath": "^1.1.1"
+ }
+ },
+ "class-utils": {
+ "version": "0.3.6",
+ "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz",
+ "integrity": "sha1-+TNprouafOAv1B+q0MqDAzGQxGM=",
+ "dev": true,
+ "requires": {
+ "arr-union": "^3.1.0",
+ "define-property": "^0.2.5",
+ "isobject": "^3.0.0",
+ "static-extend": "^0.1.1"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^0.1.0"
+ }
+ }
+ }
+ },
+ "cli": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/cli/-/cli-1.0.1.tgz",
+ "integrity": "sha1-IoF1NPJL+klQw01TLUjsvGIbjBQ=",
+ "dev": true,
+ "requires": {
+ "exit": "0.1.2",
+ "glob": "^7.1.1"
+ }
+ },
+ "cli-cursor": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz",
+ "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=",
+ "dev": true,
+ "requires": {
+ "restore-cursor": "^2.0.0"
+ }
+ },
+ "cli-width": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz",
+ "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=",
+ "dev": true
+ },
+ "cliui": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz",
+ "integrity": "sha1-NIQi2+gtgAswIu709qwQvy5NG0k=",
+ "dev": true,
+ "requires": {
+ "string-width": "^2.1.1",
+ "strip-ansi": "^4.0.0",
+ "wrap-ansi": "^2.0.0"
+ }
+ },
+ "code-point-at": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
+ "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
+ "dev": true
+ },
+ "collection-visit": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz",
+ "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=",
+ "dev": true,
+ "requires": {
+ "map-visit": "^1.0.0",
+ "object-visit": "^1.0.0"
+ }
+ },
+ "color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha1-u3GFBpDh8TZWfeYp0tVHHe2kweg=",
+ "dev": true,
+ "requires": {
+ "color-name": "1.1.3"
+ }
+ },
+ "color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+ "dev": true
+ },
+ "commander": {
+ "version": "2.19.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz",
+ "integrity": "sha1-9hmKqE5bg8RgVLlN3tv+1e6f8So=",
+ "dev": true
+ },
+ "commondir": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
+ "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=",
+ "dev": true
+ },
+ "component-emitter": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz",
+ "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=",
+ "dev": true
+ },
+ "concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
+ },
+ "console-browserify": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz",
+ "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=",
+ "dev": true,
+ "requires": {
+ "date-now": "^0.1.4"
+ }
+ },
+ "convert-source-map": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz",
+ "integrity": "sha1-UbU3qMQ+DwTewZk7/83VBOdYrCA=",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "~5.1.1"
+ }
+ },
+ "copy-descriptor": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz",
+ "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=",
+ "dev": true
+ },
+ "core-js": {
+ "version": "2.6.5",
+ "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.5.tgz",
+ "integrity": "sha1-RLyNJJ5/sv9dAOA0Gn/7lPv2eJU="
+ },
+ "core-util-is": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
+ "dev": true
+ },
+ "cross-spawn": {
+ "version": "6.0.5",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
+ "integrity": "sha1-Sl7Hxk364iw6FBJNus3uhG2Ay8Q=",
+ "dev": true,
+ "requires": {
+ "nice-try": "^1.0.4",
+ "path-key": "^2.0.1",
+ "semver": "^5.5.0",
+ "shebang-command": "^1.2.0",
+ "which": "^1.2.9"
+ }
+ },
+ "date-now": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz",
+ "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=",
+ "dev": true
+ },
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=",
+ "dev": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "decamelize": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
+ "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
+ "dev": true
+ },
+ "decode-uri-component": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
+ "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=",
+ "dev": true
+ },
+ "deep-is": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
+ "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=",
+ "dev": true
+ },
+ "define-properties": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
+ "integrity": "sha1-z4jabL7ib+bbcJT2HYcMvYTO6fE=",
+ "dev": true,
+ "requires": {
+ "object-keys": "^1.0.12"
+ }
+ },
+ "define-property": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz",
+ "integrity": "sha1-1Flono1lS6d+AqgX+HENcCyxbp0=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^1.0.2",
+ "isobject": "^3.0.1"
+ },
+ "dependencies": {
+ "is-accessor-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+ "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-data-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+ "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-descriptor": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+ "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=",
+ "dev": true,
+ "requires": {
+ "is-accessor-descriptor": "^1.0.0",
+ "is-data-descriptor": "^1.0.0",
+ "kind-of": "^6.0.2"
+ }
+ }
+ }
+ },
+ "detect-file": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz",
+ "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=",
+ "dev": true
+ },
+ "diff": {
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz",
+ "integrity": "sha1-gAwN0eCov7yVg1wgKtIg/jF+WhI=",
+ "dev": true
+ },
+ "doctrine": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
+ "integrity": "sha1-rd6+rXKmV023g2OdyHoSF3OXOWE=",
+ "dev": true,
+ "requires": {
+ "esutils": "^2.0.2"
+ }
+ },
+ "dom-serializer": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz",
+ "integrity": "sha1-HsQFnihLq+027sKUHUqXChic58A=",
+ "dev": true,
+ "requires": {
+ "domelementtype": "^1.3.0",
+ "entities": "^1.1.1"
+ },
+ "dependencies": {
+ "entities": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz",
+ "integrity": "sha1-vfpzUplmTfr9NFKe1PhSKidf6lY=",
+ "dev": true
+ }
+ }
+ },
+ "domelementtype": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz",
+ "integrity": "sha1-0EjESzew0Qp/Kj1f7j9DM9eQSB8=",
+ "dev": true
+ },
+ "domhandler": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz",
+ "integrity": "sha1-LeWaCCLVAn+r/28DLCsloqir5zg=",
+ "dev": true,
+ "requires": {
+ "domelementtype": "1"
+ }
+ },
+ "domutils": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz",
+ "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=",
+ "dev": true,
+ "requires": {
+ "dom-serializer": "0",
+ "domelementtype": "1"
+ }
+ },
+ "emoji-regex": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
+ "integrity": "sha1-kzoEBShgyF6DwSJHnEdIqOTHIVY=",
+ "dev": true
+ },
+ "end-of-stream": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz",
+ "integrity": "sha1-7SljTRm6ukY7bOa4CjchPqtx7EM=",
+ "dev": true,
+ "requires": {
+ "once": "^1.4.0"
+ }
+ },
+ "entities": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz",
+ "integrity": "sha1-sph6o4ITR/zeZCsk/fyeT7cSvyY=",
+ "dev": true
+ },
+ "es-abstract": {
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz",
+ "integrity": "sha1-rIYUX91QmdjdSVWMy6Lq+biOJOk=",
+ "dev": true,
+ "requires": {
+ "es-to-primitive": "^1.2.0",
+ "function-bind": "^1.1.1",
+ "has": "^1.0.3",
+ "is-callable": "^1.1.4",
+ "is-regex": "^1.0.4",
+ "object-keys": "^1.0.12"
+ }
+ },
+ "es-to-primitive": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz",
+ "integrity": "sha1-7fckeAM0VujdqO8J4ArZZQcH83c=",
+ "dev": true,
+ "requires": {
+ "is-callable": "^1.1.4",
+ "is-date-object": "^1.0.1",
+ "is-symbol": "^1.0.2"
+ }
+ },
+ "escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
+ "dev": true
+ },
+ "eslint": {
+ "version": "5.15.3",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.15.3.tgz",
+ "integrity": "sha1-x5w5CdyKf6NxT7NAwR4w/SUmuLU=",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.0.0",
+ "ajv": "^6.9.1",
+ "chalk": "^2.1.0",
+ "cross-spawn": "^6.0.5",
+ "debug": "^4.0.1",
+ "doctrine": "^3.0.0",
+ "eslint-scope": "^4.0.3",
+ "eslint-utils": "^1.3.1",
+ "eslint-visitor-keys": "^1.0.0",
+ "espree": "^5.0.1",
+ "esquery": "^1.0.1",
+ "esutils": "^2.0.2",
+ "file-entry-cache": "^5.0.1",
+ "functional-red-black-tree": "^1.0.1",
+ "glob": "^7.1.2",
+ "globals": "^11.7.0",
+ "ignore": "^4.0.6",
+ "import-fresh": "^3.0.0",
+ "imurmurhash": "^0.1.4",
+ "inquirer": "^6.2.2",
+ "js-yaml": "^3.12.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "levn": "^0.3.0",
+ "lodash": "^4.17.11",
+ "minimatch": "^3.0.4",
+ "mkdirp": "^0.5.1",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.8.2",
+ "path-is-inside": "^1.0.2",
+ "progress": "^2.0.0",
+ "regexpp": "^2.0.1",
+ "semver": "^5.5.1",
+ "strip-ansi": "^4.0.0",
+ "strip-json-comments": "^2.0.1",
+ "table": "^5.2.3",
+ "text-table": "^0.2.0"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+ "integrity": "sha1-O3ImAlUQnGtYnO4FDx1RYTlmR5E=",
+ "dev": true,
+ "requires": {
+ "ms": "^2.1.1"
+ }
+ },
+ "ms": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
+ "integrity": "sha1-MKWGTrPrsKZvLr5tcnrwagnYbgo=",
+ "dev": true
+ }
+ }
+ },
+ "eslint-scope": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz",
+ "integrity": "sha1-ygODMxD2iJoyZHgaqC5j65z+eEg=",
+ "dev": true,
+ "requires": {
+ "esrecurse": "^4.1.0",
+ "estraverse": "^4.1.1"
+ }
+ },
+ "eslint-utils": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.3.1.tgz",
+ "integrity": "sha1-moUbqJ7nxGA0b5fPiTnHKYgn5RI=",
+ "dev": true
+ },
+ "eslint-visitor-keys": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz",
+ "integrity": "sha1-PzGA+y4pEBdxastMnW1bXDSmqB0=",
+ "dev": true
+ },
+ "espree": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-5.0.1.tgz",
+ "integrity": "sha1-XWUm+k/H8HiKXPdbFfMDI+L4H3o=",
+ "dev": true,
+ "requires": {
+ "acorn": "^6.0.7",
+ "acorn-jsx": "^5.0.0",
+ "eslint-visitor-keys": "^1.0.0"
+ }
+ },
+ "esprima": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+ "integrity": "sha1-E7BM2z5sXRnfkatph6hpVhmwqnE=",
+ "dev": true
+ },
+ "esquery": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz",
+ "integrity": "sha1-QGxRZYsfWZGl+bYrHcJbAOPlxwg=",
+ "dev": true,
+ "requires": {
+ "estraverse": "^4.0.0"
+ }
+ },
+ "esrecurse": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz",
+ "integrity": "sha1-AHo7n9vCs7uH5IeeoZyS/b05Qs8=",
+ "dev": true,
+ "requires": {
+ "estraverse": "^4.1.0"
+ }
+ },
+ "estraverse": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz",
+ "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=",
+ "dev": true
+ },
+ "esutils": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
+ "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=",
+ "dev": true
+ },
+ "execa": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz",
+ "integrity": "sha1-xiNqW7TfbW8V6I5/AXeYIWdJ3dg=",
+ "dev": true,
+ "requires": {
+ "cross-spawn": "^6.0.0",
+ "get-stream": "^4.0.0",
+ "is-stream": "^1.1.0",
+ "npm-run-path": "^2.0.0",
+ "p-finally": "^1.0.0",
+ "signal-exit": "^3.0.0",
+ "strip-eof": "^1.0.0"
+ }
+ },
+ "exit": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz",
+ "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=",
+ "dev": true
+ },
+ "expand-brackets": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz",
+ "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=",
+ "dev": true,
+ "requires": {
+ "debug": "^2.3.3",
+ "define-property": "^0.2.5",
+ "extend-shallow": "^2.0.1",
+ "posix-character-classes": "^0.1.0",
+ "regex-not": "^1.0.0",
+ "snapdragon": "^0.8.1",
+ "to-regex": "^3.0.1"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^0.1.0"
+ }
+ },
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "dev": true,
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ }
+ }
+ },
+ "expand-tilde": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz",
+ "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=",
+ "dev": true,
+ "requires": {
+ "homedir-polyfill": "^1.0.1"
+ }
+ },
+ "extend-shallow": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
+ "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=",
+ "dev": true,
+ "requires": {
+ "assign-symbols": "^1.0.0",
+ "is-extendable": "^1.0.1"
+ },
+ "dependencies": {
+ "is-extendable": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
+ "integrity": "sha1-p0cPnkJnM9gb2B4RVSZOOjUHyrQ=",
+ "dev": true,
+ "requires": {
+ "is-plain-object": "^2.0.4"
+ }
+ }
+ }
+ },
+ "external-editor": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.0.3.tgz",
+ "integrity": "sha1-WGbbKal4Jtvkvzr9JAcOrZ6kOic=",
+ "dev": true,
+ "requires": {
+ "chardet": "^0.7.0",
+ "iconv-lite": "^0.4.24",
+ "tmp": "^0.0.33"
+ }
+ },
+ "extglob": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz",
+ "integrity": "sha1-rQD+TcYSqSMuhxhxHcXLWrAoVUM=",
+ "dev": true,
+ "requires": {
+ "array-unique": "^0.3.2",
+ "define-property": "^1.0.0",
+ "expand-brackets": "^2.1.4",
+ "extend-shallow": "^2.0.1",
+ "fragment-cache": "^0.2.1",
+ "regex-not": "^1.0.0",
+ "snapdragon": "^0.8.1",
+ "to-regex": "^3.0.1"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
+ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^1.0.0"
+ }
+ },
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "dev": true,
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ },
+ "is-accessor-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+ "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-data-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+ "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-descriptor": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+ "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=",
+ "dev": true,
+ "requires": {
+ "is-accessor-descriptor": "^1.0.0",
+ "is-data-descriptor": "^1.0.0",
+ "kind-of": "^6.0.2"
+ }
+ }
+ }
+ },
+ "fast-deep-equal": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz",
+ "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=",
+ "dev": true
+ },
+ "fast-json-stable-stringify": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
+ "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=",
+ "dev": true
+ },
+ "fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
+ "dev": true
+ },
+ "figures": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz",
+ "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=",
+ "dev": true,
+ "requires": {
+ "escape-string-regexp": "^1.0.5"
+ }
+ },
+ "file-entry-cache": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz",
+ "integrity": "sha1-yg9u+m3T1WEzP7FFFQZcL6/fQ5w=",
+ "dev": true,
+ "requires": {
+ "flat-cache": "^2.0.1"
+ }
+ },
+ "fill-range": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
+ "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=",
+ "dev": true,
+ "requires": {
+ "extend-shallow": "^2.0.1",
+ "is-number": "^3.0.0",
+ "repeat-string": "^1.6.1",
+ "to-regex-range": "^2.1.0"
+ },
+ "dependencies": {
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "dev": true,
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ }
+ }
+ },
+ "find-cache-dir": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz",
+ "integrity": "sha1-jQ+UzRP+Q8bHwmGg2GEVypGMBfc=",
+ "dev": true,
+ "requires": {
+ "commondir": "^1.0.1",
+ "make-dir": "^2.0.0",
+ "pkg-dir": "^3.0.0"
+ }
+ },
+ "find-up": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
+ "integrity": "sha1-SRafHXmTQwZG2mHsxa41XCHJe3M=",
+ "dev": true,
+ "requires": {
+ "locate-path": "^3.0.0"
+ }
+ },
+ "findup-sync": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz",
+ "integrity": "sha1-kyaxSIwi0aYIhlCoaQGy2akKLLw=",
+ "dev": true,
+ "requires": {
+ "detect-file": "^1.0.0",
+ "is-glob": "^3.1.0",
+ "micromatch": "^3.0.4",
+ "resolve-dir": "^1.0.1"
+ },
+ "dependencies": {
+ "is-glob": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz",
+ "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=",
+ "dev": true,
+ "requires": {
+ "is-extglob": "^2.1.0"
+ }
+ }
+ }
+ },
+ "flat": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz",
+ "integrity": "sha1-CQvsiwXjnLowl0fx1YjwTbr5jbI=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "~2.0.3"
+ },
+ "dependencies": {
+ "is-buffer": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz",
+ "integrity": "sha1-Ts8/z3ScvR5HJonhCaxmJhol5yU=",
+ "dev": true
+ }
+ }
+ },
+ "flat-cache": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz",
+ "integrity": "sha1-XSltbwS9pEpGMKMBQTvbwuwIXsA=",
+ "dev": true,
+ "requires": {
+ "flatted": "^2.0.0",
+ "rimraf": "2.6.3",
+ "write": "1.0.3"
+ }
+ },
+ "flatted": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.0.tgz",
+ "integrity": "sha1-VRIrZTbqSWtLRIk+4mCBQdENmRY=",
+ "dev": true
+ },
+ "for-in": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
+ "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=",
+ "dev": true
+ },
+ "fragment-cache": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz",
+ "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=",
+ "dev": true,
+ "requires": {
+ "map-cache": "^0.2.2"
+ }
+ },
+ "fs-readdir-recursive": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz",
+ "integrity": "sha1-4y/AMKLM7kSmtTcTCNpUvgs5fSc=",
+ "dev": true
+ },
+ "fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
+ },
+ "fsevents": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.7.tgz",
+ "integrity": "sha512-Pxm6sI2MeBD7RdD12RYsqaP0nMiwx8eZBXCa6z2L+mRHm2DYrOYwihmhjpkdjUHwQhslWQjRpEgNq4XvBmaAuw==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "nan": "^2.9.2",
+ "node-pre-gyp": "^0.10.0"
+ },
+ "dependencies": {
+ "abbrev": {
+ "version": "1.1.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "ansi-regex": {
+ "version": "2.1.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "aproba": {
+ "version": "1.2.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "are-we-there-yet": {
+ "version": "1.1.5",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "delegates": "^1.0.0",
+ "readable-stream": "^2.0.6"
+ }
+ },
+ "balanced-match": {
+ "version": "1.0.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "brace-expansion": {
+ "version": "1.1.11",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "chownr": {
+ "version": "1.1.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "code-point-at": {
+ "version": "1.1.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "concat-map": {
+ "version": "0.0.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "console-control-strings": {
+ "version": "1.1.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "core-util-is": {
+ "version": "1.0.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "debug": {
+ "version": "2.6.9",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "deep-extend": {
+ "version": "0.6.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "delegates": {
+ "version": "1.0.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "detect-libc": {
+ "version": "1.0.3",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "fs-minipass": {
+ "version": "1.2.5",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "minipass": "^2.2.1"
+ }
+ },
+ "fs.realpath": {
+ "version": "1.0.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "gauge": {
+ "version": "2.7.4",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "aproba": "^1.0.3",
+ "console-control-strings": "^1.0.0",
+ "has-unicode": "^2.0.0",
+ "object-assign": "^4.1.0",
+ "signal-exit": "^3.0.0",
+ "string-width": "^1.0.1",
+ "strip-ansi": "^3.0.1",
+ "wide-align": "^1.1.0"
+ }
+ },
+ "glob": {
+ "version": "7.1.3",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ },
+ "has-unicode": {
+ "version": "2.0.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "iconv-lite": {
+ "version": "0.4.24",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ }
+ },
+ "ignore-walk": {
+ "version": "3.0.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "minimatch": "^3.0.4"
+ }
+ },
+ "inflight": {
+ "version": "1.0.6",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "inherits": {
+ "version": "2.0.3",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "ini": {
+ "version": "1.3.5",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "is-fullwidth-code-point": {
+ "version": "1.0.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "number-is-nan": "^1.0.0"
+ }
+ },
+ "isarray": {
+ "version": "1.0.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "minimatch": {
+ "version": "3.0.4",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "brace-expansion": "^1.1.7"
+ }
+ },
+ "minimist": {
+ "version": "0.0.8",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "minipass": {
+ "version": "2.3.5",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "safe-buffer": "^5.1.2",
+ "yallist": "^3.0.0"
+ }
+ },
+ "minizlib": {
+ "version": "1.2.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "minipass": "^2.2.1"
+ }
+ },
+ "mkdirp": {
+ "version": "0.5.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "minimist": "0.0.8"
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "needle": {
+ "version": "2.2.4",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "debug": "^2.1.2",
+ "iconv-lite": "^0.4.4",
+ "sax": "^1.2.4"
+ }
+ },
+ "node-pre-gyp": {
+ "version": "0.10.3",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "detect-libc": "^1.0.2",
+ "mkdirp": "^0.5.1",
+ "needle": "^2.2.1",
+ "nopt": "^4.0.1",
+ "npm-packlist": "^1.1.6",
+ "npmlog": "^4.0.2",
+ "rc": "^1.2.7",
+ "rimraf": "^2.6.1",
+ "semver": "^5.3.0",
+ "tar": "^4"
+ }
+ },
+ "nopt": {
+ "version": "4.0.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "abbrev": "1",
+ "osenv": "^0.1.4"
+ }
+ },
+ "npm-bundled": {
+ "version": "1.0.5",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "npm-packlist": {
+ "version": "1.2.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "ignore-walk": "^3.0.1",
+ "npm-bundled": "^1.0.1"
+ }
+ },
+ "npmlog": {
+ "version": "4.1.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "are-we-there-yet": "~1.1.2",
+ "console-control-strings": "~1.1.0",
+ "gauge": "~2.7.3",
+ "set-blocking": "~2.0.0"
+ }
+ },
+ "number-is-nan": {
+ "version": "1.0.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "object-assign": {
+ "version": "4.1.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "once": {
+ "version": "1.4.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "wrappy": "1"
+ }
+ },
+ "os-homedir": {
+ "version": "1.0.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "os-tmpdir": {
+ "version": "1.0.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "osenv": {
+ "version": "0.1.5",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "os-homedir": "^1.0.0",
+ "os-tmpdir": "^1.0.0"
+ }
+ },
+ "path-is-absolute": {
+ "version": "1.0.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "process-nextick-args": {
+ "version": "2.0.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "rc": {
+ "version": "1.2.8",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "deep-extend": "^0.6.0",
+ "ini": "~1.3.0",
+ "minimist": "^1.2.0",
+ "strip-json-comments": "~2.0.1"
+ },
+ "dependencies": {
+ "minimist": {
+ "version": "1.2.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ }
+ }
+ },
+ "readable-stream": {
+ "version": "2.3.6",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "rimraf": {
+ "version": "2.6.3",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "glob": "^7.1.3"
+ }
+ },
+ "safe-buffer": {
+ "version": "5.1.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "safer-buffer": {
+ "version": "2.1.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "sax": {
+ "version": "1.2.4",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "semver": {
+ "version": "5.6.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "set-blocking": {
+ "version": "2.0.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "signal-exit": {
+ "version": "3.0.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "string-width": {
+ "version": "1.0.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "code-point-at": "^1.0.0",
+ "is-fullwidth-code-point": "^1.0.0",
+ "strip-ansi": "^3.0.0"
+ }
+ },
+ "string_decoder": {
+ "version": "1.1.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "safe-buffer": "~5.1.0"
+ }
+ },
+ "strip-ansi": {
+ "version": "3.0.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "ansi-regex": "^2.0.0"
+ }
+ },
+ "strip-json-comments": {
+ "version": "2.0.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "tar": {
+ "version": "4.4.8",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "chownr": "^1.1.1",
+ "fs-minipass": "^1.2.5",
+ "minipass": "^2.3.4",
+ "minizlib": "^1.1.1",
+ "mkdirp": "^0.5.0",
+ "safe-buffer": "^5.1.2",
+ "yallist": "^3.0.2"
+ }
+ },
+ "util-deprecate": {
+ "version": "1.0.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "wide-align": {
+ "version": "1.1.3",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "string-width": "^1.0.2 || 2"
+ }
+ },
+ "wrappy": {
+ "version": "1.0.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "yallist": {
+ "version": "3.0.3",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ }
+ }
+ },
+ "function-bind": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+ "integrity": "sha1-pWiZ0+o8m6uHS7l3O3xe3pL0iV0=",
+ "dev": true
+ },
+ "functional-red-black-tree": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
+ "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=",
+ "dev": true
+ },
+ "get-caller-file": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz",
+ "integrity": "sha1-+Xj6TJDR3+f/LWvtoqUV5xO9z0o=",
+ "dev": true
+ },
+ "get-stream": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
+ "integrity": "sha1-wbJVV189wh1Zv8ec09K0axw6VLU=",
+ "dev": true,
+ "requires": {
+ "pump": "^3.0.0"
+ }
+ },
+ "get-value": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz",
+ "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=",
+ "dev": true
+ },
+ "glob": {
+ "version": "7.1.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz",
+ "integrity": "sha1-OWCDLT8VdBCDQtr9OmezMsCWnfE=",
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ },
+ "glob-parent": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
+ "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "is-glob": "^3.1.0",
+ "path-dirname": "^1.0.0"
+ },
+ "dependencies": {
+ "is-glob": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz",
+ "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "is-extglob": "^2.1.0"
+ }
+ }
+ }
+ },
+ "global-modules": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz",
+ "integrity": "sha1-bXcPDrUjrHgWTXK15xqIdyZcw+o=",
+ "dev": true,
+ "requires": {
+ "global-prefix": "^1.0.1",
+ "is-windows": "^1.0.1",
+ "resolve-dir": "^1.0.0"
+ }
+ },
+ "global-prefix": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz",
+ "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=",
+ "dev": true,
+ "requires": {
+ "expand-tilde": "^2.0.2",
+ "homedir-polyfill": "^1.0.1",
+ "ini": "^1.3.4",
+ "is-windows": "^1.0.1",
+ "which": "^1.2.14"
+ }
+ },
+ "globals": {
+ "version": "11.11.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-11.11.0.tgz",
+ "integrity": "sha1-3Pk3V/ot5Uhvvu1xGFOK33ienC4=",
+ "dev": true
+ },
+ "gonzales-pe": {
+ "version": "4.2.4",
+ "resolved": "https://registry.npmjs.org/gonzales-pe/-/gonzales-pe-4.2.4.tgz",
+ "integrity": "sha1-NWrjajEsRv4PECbdbLU5A5+FANI=",
+ "requires": {
+ "minimist": "1.1.x"
+ },
+ "dependencies": {
+ "minimist": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.1.3.tgz",
+ "integrity": "sha1-O+39kaktOQFvz6ocaB6Pqhoe/ag="
+ }
+ }
+ },
+ "graceful-fs": {
+ "version": "4.1.15",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz",
+ "integrity": "sha1-/7cD4QZuig7qpMi4C6klPu77+wA=",
+ "dev": true
+ },
+ "growl": {
+ "version": "1.10.5",
+ "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz",
+ "integrity": "sha1-8nNdwig2dPpnR4sQGBBZNVw2nl4=",
+ "dev": true
+ },
+ "has": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+ "integrity": "sha1-ci18v8H2qoJB8W3YFOAR4fQeh5Y=",
+ "dev": true,
+ "requires": {
+ "function-bind": "^1.1.1"
+ }
+ },
+ "has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+ "dev": true
+ },
+ "has-symbols": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz",
+ "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=",
+ "dev": true
+ },
+ "has-value": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz",
+ "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=",
+ "dev": true,
+ "requires": {
+ "get-value": "^2.0.6",
+ "has-values": "^1.0.0",
+ "isobject": "^3.0.0"
+ }
+ },
+ "has-values": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz",
+ "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=",
+ "dev": true,
+ "requires": {
+ "is-number": "^3.0.0",
+ "kind-of": "^4.0.0"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz",
+ "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "he": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
+ "integrity": "sha1-hK5l+n6vsWX922FWauFLrwVmTw8=",
+ "dev": true
+ },
+ "homedir-polyfill": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz",
+ "integrity": "sha1-dDKYzvTlrz4ZQWH7rcwhUdOgWOg=",
+ "dev": true,
+ "requires": {
+ "parse-passwd": "^1.0.0"
+ }
+ },
+ "htmlparser2": {
+ "version": "3.8.3",
+ "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz",
+ "integrity": "sha1-mWwosZFRaovoZQGn15dX5ccMEGg=",
+ "dev": true,
+ "requires": {
+ "domelementtype": "1",
+ "domhandler": "2.3",
+ "domutils": "1.5",
+ "entities": "1.0",
+ "readable-stream": "1.1"
+ },
+ "dependencies": {
+ "isarray": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+ "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=",
+ "dev": true
+ },
+ "readable-stream": {
+ "version": "1.1.14",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
+ "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
+ "dev": true,
+ "requires": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.1",
+ "isarray": "0.0.1",
+ "string_decoder": "~0.10.x"
+ }
+ },
+ "string_decoder": {
+ "version": "0.10.31",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
+ "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=",
+ "dev": true
+ }
+ }
+ },
+ "iconv-lite": {
+ "version": "0.4.24",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+ "integrity": "sha1-ICK0sl+93CHS9SSXSkdKr+czkIs=",
+ "dev": true,
+ "requires": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ }
+ },
+ "ignore": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
+ "integrity": "sha1-dQ49tYYgh7RzfrrIIH/9HvJ7Jfw=",
+ "dev": true
+ },
+ "import-fresh": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.0.0.tgz",
+ "integrity": "sha1-o9iX9CDKsOZxI2iX91vBS0iFw5A=",
+ "dev": true,
+ "requires": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ }
+ },
+ "imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
+ "dev": true
+ },
+ "inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+ "requires": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "inherits": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
+ },
+ "ini": {
+ "version": "1.3.5",
+ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
+ "integrity": "sha1-7uJfVtscnsYIXgwid4CD9Zar+Sc=",
+ "dev": true
+ },
+ "inquirer": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.2.2.tgz",
+ "integrity": "sha1-RpQRdvZcnrIIBGJxSbdDohjyVAY=",
+ "dev": true,
+ "requires": {
+ "ansi-escapes": "^3.2.0",
+ "chalk": "^2.4.2",
+ "cli-cursor": "^2.1.0",
+ "cli-width": "^2.0.0",
+ "external-editor": "^3.0.3",
+ "figures": "^2.0.0",
+ "lodash": "^4.17.11",
+ "mute-stream": "0.0.7",
+ "run-async": "^2.2.0",
+ "rxjs": "^6.4.0",
+ "string-width": "^2.1.0",
+ "strip-ansi": "^5.0.0",
+ "through": "^2.3.6"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+ "integrity": "sha1-i5+PCM8ay4Q3Vqg5yox+MWjFGZc=",
+ "dev": true
+ },
+ "strip-ansi": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+ "integrity": "sha1-jJpTb+tq/JYr36WxBKUJHBrZwK4=",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^4.1.0"
+ }
+ }
+ }
+ },
+ "invert-kv": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz",
+ "integrity": "sha1-c5P1r6Weyf9fZ6J2INEcIm4+7AI=",
+ "dev": true
+ },
+ "is-accessor-descriptor": {
+ "version": "0.1.6",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
+ "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=",
+ "dev": true,
+ "requires": {
+ "kind-of": "^3.0.2"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "is-binary-path": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz",
+ "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "binary-extensions": "^1.0.0"
+ }
+ },
+ "is-buffer": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
+ "integrity": "sha1-76ouqdqg16suoTqXsritUf776L4=",
+ "dev": true
+ },
+ "is-callable": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz",
+ "integrity": "sha1-HhrfIZ4e62hNaR+dagX/DTCiTXU=",
+ "dev": true
+ },
+ "is-data-descriptor": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
+ "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=",
+ "dev": true,
+ "requires": {
+ "kind-of": "^3.0.2"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "is-date-object": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz",
+ "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=",
+ "dev": true
+ },
+ "is-descriptor": {
+ "version": "0.1.6",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz",
+ "integrity": "sha1-Nm2CQN3kh8pRgjsaufB6EKeCUco=",
+ "dev": true,
+ "requires": {
+ "is-accessor-descriptor": "^0.1.6",
+ "is-data-descriptor": "^0.1.4",
+ "kind-of": "^5.0.0"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz",
+ "integrity": "sha1-cpyR4thXt6QZofmqZWhcTDP1hF0=",
+ "dev": true
+ }
+ }
+ },
+ "is-extendable": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
+ "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=",
+ "dev": true
+ },
+ "is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
+ "dev": true
+ },
+ "is-fullwidth-code-point": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+ "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+ "dev": true
+ },
+ "is-glob": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz",
+ "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "is-extglob": "^2.1.1"
+ }
+ },
+ "is-number": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
+ "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
+ "dev": true,
+ "requires": {
+ "kind-of": "^3.0.2"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "is-plain-obj": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz",
+ "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=",
+ "dev": true
+ },
+ "is-plain-object": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
+ "integrity": "sha1-LBY7P6+xtgbZ0Xko8FwqHDjgdnc=",
+ "dev": true,
+ "requires": {
+ "isobject": "^3.0.1"
+ }
+ },
+ "is-promise": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz",
+ "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=",
+ "dev": true
+ },
+ "is-regex": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz",
+ "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=",
+ "dev": true,
+ "requires": {
+ "has": "^1.0.1"
+ }
+ },
+ "is-stream": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
+ "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=",
+ "dev": true
+ },
+ "is-symbol": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz",
+ "integrity": "sha1-oFX2rlcZLK7jKeeoYBGLSXqVDzg=",
+ "dev": true,
+ "requires": {
+ "has-symbols": "^1.0.0"
+ }
+ },
+ "is-windows": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz",
+ "integrity": "sha1-0YUOuXkezRjmGCzhKjDzlmNLsZ0=",
+ "dev": true
+ },
+ "isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
+ "dev": true
+ },
+ "isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
+ "dev": true
+ },
+ "isobject": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
+ "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
+ "dev": true
+ },
+ "js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha1-GSA/tZmR35jjoocFDUZHzerzJJk=",
+ "dev": true
+ },
+ "js-yaml": {
+ "version": "3.13.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.0.tgz",
+ "integrity": "sha1-OO5xeKwO6iyX/22W//SxjH2M+Y4=",
+ "dev": true,
+ "requires": {
+ "argparse": "^1.0.7",
+ "esprima": "^4.0.0"
+ }
+ },
+ "jsesc": {
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
+ "integrity": "sha1-gFZNLkg9rPbo7yCWUKZ98/DCg6Q=",
+ "dev": true
+ },
+ "jshint": {
+ "version": "2.10.2",
+ "resolved": "https://registry.npmjs.org/jshint/-/jshint-2.10.2.tgz",
+ "integrity": "sha1-7WYmxPgiPJjpSq6mJ2dDVCekmj0=",
+ "dev": true,
+ "requires": {
+ "cli": "~1.0.0",
+ "console-browserify": "1.1.x",
+ "exit": "0.1.x",
+ "htmlparser2": "3.8.x",
+ "lodash": "~4.17.11",
+ "minimatch": "~3.0.2",
+ "shelljs": "0.3.x",
+ "strip-json-comments": "1.0.x"
+ },
+ "dependencies": {
+ "strip-json-comments": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz",
+ "integrity": "sha1-HhX7ysl9Pumb8tc7TGVrCCu6+5E=",
+ "dev": true
+ }
+ }
+ },
+ "json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha1-afaofZUTq4u4/mO9sJecRI5oRmA=",
+ "dev": true
+ },
+ "json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=",
+ "dev": true
+ },
+ "json5": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.0.tgz",
+ "integrity": "sha1-56DGLEgoXGKNIKELhcibuAfDKFA=",
+ "dev": true,
+ "requires": {
+ "minimist": "^1.2.0"
+ }
+ },
+ "kind-of": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
+ "integrity": "sha1-ARRrNqYhjmTljzqNZt5df8b20FE=",
+ "dev": true
+ },
+ "lcid": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz",
+ "integrity": "sha1-bvXS32DlL4LrIopMNz6NHzlyU88=",
+ "dev": true,
+ "requires": {
+ "invert-kv": "^2.0.0"
+ }
+ },
+ "levn": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
+ "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=",
+ "dev": true,
+ "requires": {
+ "prelude-ls": "~1.1.2",
+ "type-check": "~0.3.2"
+ }
+ },
+ "locate-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
+ "integrity": "sha1-2+w7OrdZdYBxtY/ln8QYca8hQA4=",
+ "dev": true,
+ "requires": {
+ "p-locate": "^3.0.0",
+ "path-exists": "^3.0.0"
+ }
+ },
+ "lodash": {
+ "version": "4.17.11",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
+ "integrity": "sha1-s56mIp72B+zYniyN8SU2iRysm40=",
+ "dev": true
+ },
+ "log-symbols": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz",
+ "integrity": "sha1-V0Dhxdbw39pK2TI7UzIQfva0xAo=",
+ "dev": true,
+ "requires": {
+ "chalk": "^2.0.1"
+ }
+ },
+ "make-dir": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
+ "integrity": "sha1-XwMQ4YuL6JjMBwCSlaMK5B6R5vU=",
+ "dev": true,
+ "requires": {
+ "pify": "^4.0.1",
+ "semver": "^5.6.0"
+ }
+ },
+ "map-age-cleaner": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz",
+ "integrity": "sha1-fVg6cwZDTAVf5HSw9FB45uG0uSo=",
+ "dev": true,
+ "requires": {
+ "p-defer": "^1.0.0"
+ }
+ },
+ "map-cache": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz",
+ "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=",
+ "dev": true
+ },
+ "map-visit": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz",
+ "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=",
+ "dev": true,
+ "requires": {
+ "object-visit": "^1.0.0"
+ }
+ },
+ "mem": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/mem/-/mem-4.2.0.tgz",
+ "integrity": "sha1-XuBXaA7Zy42tinjYIPmoiXoQICU=",
+ "dev": true,
+ "requires": {
+ "map-age-cleaner": "^0.1.1",
+ "mimic-fn": "^2.0.0",
+ "p-is-promise": "^2.0.0"
+ },
+ "dependencies": {
+ "mimic-fn": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.0.0.tgz",
+ "integrity": "sha1-CRP/CxIdtE71hIJCw4u7NdRMq94=",
+ "dev": true
+ }
+ }
+ },
+ "micromatch": {
+ "version": "3.1.10",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
+ "integrity": "sha1-cIWbyVyYQJUvNZoGij/En57PrCM=",
+ "dev": true,
+ "requires": {
+ "arr-diff": "^4.0.0",
+ "array-unique": "^0.3.2",
+ "braces": "^2.3.1",
+ "define-property": "^2.0.2",
+ "extend-shallow": "^3.0.2",
+ "extglob": "^2.0.4",
+ "fragment-cache": "^0.2.1",
+ "kind-of": "^6.0.2",
+ "nanomatch": "^1.2.9",
+ "object.pick": "^1.3.0",
+ "regex-not": "^1.0.0",
+ "snapdragon": "^0.8.1",
+ "to-regex": "^3.0.2"
+ }
+ },
+ "mimic-fn": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz",
+ "integrity": "sha1-ggyGo5M0ZA6ZUWkovQP8qIBX0CI=",
+ "dev": true
+ },
+ "minimatch": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+ "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=",
+ "requires": {
+ "brace-expansion": "^1.1.7"
+ }
+ },
+ "minimist": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+ "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
+ },
+ "mixin-deep": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz",
+ "integrity": "sha1-pJ5yaNzhoNlpjkUybFYm3zVD0P4=",
+ "dev": true,
+ "requires": {
+ "for-in": "^1.0.2",
+ "is-extendable": "^1.0.1"
+ },
+ "dependencies": {
+ "is-extendable": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
+ "integrity": "sha1-p0cPnkJnM9gb2B4RVSZOOjUHyrQ=",
+ "dev": true,
+ "requires": {
+ "is-plain-object": "^2.0.4"
+ }
+ }
+ }
+ },
+ "mkdirp": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+ "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
+ "dev": true,
+ "requires": {
+ "minimist": "0.0.8"
+ },
+ "dependencies": {
+ "minimist": {
+ "version": "0.0.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
+ "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
+ "dev": true
+ }
+ }
+ },
+ "mocha": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.0.2.tgz",
+ "integrity": "sha1-zcGm/fZkcsB5tWBbrFnSmAdwLSw=",
+ "dev": true,
+ "requires": {
+ "ansi-colors": "3.2.3",
+ "browser-stdout": "1.3.1",
+ "debug": "3.2.6",
+ "diff": "3.5.0",
+ "escape-string-regexp": "1.0.5",
+ "findup-sync": "2.0.0",
+ "glob": "7.1.3",
+ "growl": "1.10.5",
+ "he": "1.2.0",
+ "js-yaml": "3.12.0",
+ "log-symbols": "2.2.0",
+ "minimatch": "3.0.4",
+ "mkdirp": "0.5.1",
+ "ms": "2.1.1",
+ "node-environment-flags": "1.0.4",
+ "object.assign": "4.1.0",
+ "strip-json-comments": "2.0.1",
+ "supports-color": "6.0.0",
+ "which": "1.3.1",
+ "wide-align": "1.1.3",
+ "yargs": "12.0.5",
+ "yargs-parser": "11.1.1",
+ "yargs-unparser": "1.5.0"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "3.2.6",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
+ "integrity": "sha1-6D0X3hbYp++3cX7b5fsQE17uYps=",
+ "dev": true,
+ "requires": {
+ "ms": "^2.1.1"
+ }
+ },
+ "js-yaml": {
+ "version": "3.12.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz",
+ "integrity": "sha1-6u1lbsg0TxD1J8a/obbiJE3hZ9E=",
+ "dev": true,
+ "requires": {
+ "argparse": "^1.0.7",
+ "esprima": "^4.0.0"
+ }
+ },
+ "ms": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
+ "integrity": "sha1-MKWGTrPrsKZvLr5tcnrwagnYbgo=",
+ "dev": true
+ },
+ "supports-color": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz",
+ "integrity": "sha1-ds/nQs8fQbubHCmtAwaMBbTA5Ao=",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ }
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+ "dev": true
+ },
+ "mute-stream": {
+ "version": "0.0.7",
+ "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz",
+ "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=",
+ "dev": true
+ },
+ "nan": {
+ "version": "2.13.1",
+ "resolved": "https://registry.npmjs.org/nan/-/nan-2.13.1.tgz",
+ "integrity": "sha512-I6YB/YEuDeUZMmhscXKxGgZlFnhsn5y0hgOZBadkzfTRrZBtJDZeg6eQf7PYMIEclwmorTKK8GztsyOUSVBREA==",
+ "dev": true,
+ "optional": true
+ },
+ "nanomatch": {
+ "version": "1.2.13",
+ "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz",
+ "integrity": "sha1-uHqKpPwN6P5r6IiVs4mD/yZb0Rk=",
+ "dev": true,
+ "requires": {
+ "arr-diff": "^4.0.0",
+ "array-unique": "^0.3.2",
+ "define-property": "^2.0.2",
+ "extend-shallow": "^3.0.2",
+ "fragment-cache": "^0.2.1",
+ "is-windows": "^1.0.2",
+ "kind-of": "^6.0.2",
+ "object.pick": "^1.3.0",
+ "regex-not": "^1.0.0",
+ "snapdragon": "^0.8.1",
+ "to-regex": "^3.0.1"
+ }
+ },
+ "natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=",
+ "dev": true
+ },
+ "nice-try": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
+ "integrity": "sha1-ozeKdpbOfSI+iPybdkvX7xCJ42Y=",
+ "dev": true
+ },
+ "node-environment-flags": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.4.tgz",
+ "integrity": "sha1-C3hKZVFCa/wW07IghCTcvCsv8Dg=",
+ "dev": true,
+ "requires": {
+ "object.getownpropertydescriptors": "^2.0.3"
+ }
+ },
+ "node-modules-regexp": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz",
+ "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=",
+ "dev": true
+ },
+ "normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha1-Dc1p/yOhybEf0JeDFmRKA4ghamU=",
+ "dev": true,
+ "optional": true
+ },
+ "npm-run-path": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
+ "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=",
+ "dev": true,
+ "requires": {
+ "path-key": "^2.0.0"
+ }
+ },
+ "number-is-nan": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
+ "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
+ "dev": true
+ },
+ "object-copy": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz",
+ "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=",
+ "dev": true,
+ "requires": {
+ "copy-descriptor": "^0.1.0",
+ "define-property": "^0.2.5",
+ "kind-of": "^3.0.3"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^0.1.0"
+ }
+ },
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "object-keys": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.0.tgz",
+ "integrity": "sha1-Eb0iNI3S4JagRasG9shbzDQPoDI=",
+ "dev": true
+ },
+ "object-visit": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz",
+ "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=",
+ "dev": true,
+ "requires": {
+ "isobject": "^3.0.0"
+ }
+ },
+ "object.assign": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz",
+ "integrity": "sha1-lovxEA15Vrs8oIbwBvhGs7xACNo=",
+ "dev": true,
+ "requires": {
+ "define-properties": "^1.1.2",
+ "function-bind": "^1.1.1",
+ "has-symbols": "^1.0.0",
+ "object-keys": "^1.0.11"
+ }
+ },
+ "object.getownpropertydescriptors": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz",
+ "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=",
+ "dev": true,
+ "requires": {
+ "define-properties": "^1.1.2",
+ "es-abstract": "^1.5.1"
+ }
+ },
+ "object.pick": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz",
+ "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=",
+ "dev": true,
+ "requires": {
+ "isobject": "^3.0.1"
+ }
+ },
+ "once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+ "requires": {
+ "wrappy": "1"
+ }
+ },
+ "onetime": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz",
+ "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=",
+ "dev": true,
+ "requires": {
+ "mimic-fn": "^1.0.0"
+ }
+ },
+ "optionator": {
+ "version": "0.8.2",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz",
+ "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=",
+ "dev": true,
+ "requires": {
+ "deep-is": "~0.1.3",
+ "fast-levenshtein": "~2.0.4",
+ "levn": "~0.3.0",
+ "prelude-ls": "~1.1.2",
+ "type-check": "~0.3.2",
+ "wordwrap": "~1.0.0"
+ }
+ },
+ "os-locale": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz",
+ "integrity": "sha1-qAKm7hfyTBBIOrmTVxnO9O0Wvxo=",
+ "dev": true,
+ "requires": {
+ "execa": "^1.0.0",
+ "lcid": "^2.0.0",
+ "mem": "^4.0.0"
+ }
+ },
+ "os-tmpdir": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
+ "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
+ "dev": true
+ },
+ "output-file-sync": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/output-file-sync/-/output-file-sync-2.0.1.tgz",
+ "integrity": "sha1-9TEYKC9fVTwnmVQXkrcjpMcUMMA=",
+ "dev": true,
+ "requires": {
+ "graceful-fs": "^4.1.11",
+ "is-plain-obj": "^1.1.0",
+ "mkdirp": "^0.5.1"
+ }
+ },
+ "p-defer": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz",
+ "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=",
+ "dev": true
+ },
+ "p-finally": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
+ "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=",
+ "dev": true
+ },
+ "p-is-promise": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.0.0.tgz",
+ "integrity": "sha1-dVTj1XIQmofh8/U/an2F0bGU9MU=",
+ "dev": true
+ },
+ "p-limit": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz",
+ "integrity": "sha1-QXyZQeYCepq8ulCS3SkE4lW1+8I=",
+ "dev": true,
+ "requires": {
+ "p-try": "^2.0.0"
+ }
+ },
+ "p-locate": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
+ "integrity": "sha1-Mi1poFwCZLJZl9n0DNiokasAZKQ=",
+ "dev": true,
+ "requires": {
+ "p-limit": "^2.0.0"
+ }
+ },
+ "p-try": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.1.0.tgz",
+ "integrity": "sha1-waDxAw6X3gGLsscYkp0q9ZRj5QU=",
+ "dev": true
+ },
+ "parent-module": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.0.tgz",
+ "integrity": "sha1-3yUL3FOR9KCF+1idrXYfWta4ZbU=",
+ "dev": true,
+ "requires": {
+ "callsites": "^3.0.0"
+ }
+ },
+ "parse-passwd": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz",
+ "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=",
+ "dev": true
+ },
+ "pascalcase": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz",
+ "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=",
+ "dev": true
+ },
+ "path-dirname": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz",
+ "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=",
+ "dev": true,
+ "optional": true
+ },
+ "path-exists": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
+ "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
+ "dev": true
+ },
+ "path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
+ },
+ "path-is-inside": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz",
+ "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=",
+ "dev": true
+ },
+ "path-key": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
+ "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=",
+ "dev": true
+ },
+ "path-parse": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
+ "integrity": "sha1-1i27VnlAXXLEc37FhgDp3c8G0kw=",
+ "dev": true
+ },
+ "pify": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
+ "integrity": "sha1-SyzSXFDVmHNcUCkiJP2MbfQeMjE=",
+ "dev": true
+ },
+ "pirates": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz",
+ "integrity": "sha1-ZDqSyviUVm+RsrmG0sZpUKji+4c=",
+ "dev": true,
+ "requires": {
+ "node-modules-regexp": "^1.0.0"
+ }
+ },
+ "pkg-dir": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz",
+ "integrity": "sha1-J0kCDyOe2ZCIGx9xIQ1R62UjvqM=",
+ "dev": true,
+ "requires": {
+ "find-up": "^3.0.0"
+ }
+ },
+ "posix-character-classes": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz",
+ "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=",
+ "dev": true
+ },
+ "prelude-ls": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
+ "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=",
+ "dev": true
+ },
+ "process-nextick-args": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz",
+ "integrity": "sha1-o31zL0JxtKsa0HDTVQjoKQeI/6o=",
+ "dev": true,
+ "optional": true
+ },
+ "progress": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
+ "integrity": "sha1-foz42PW48jnBvGi+tOt4Vn1XLvg=",
+ "dev": true
+ },
+ "pump": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
+ "integrity": "sha1-tKIRaBW94vTh6mAjVOjHVWUQemQ=",
+ "dev": true,
+ "requires": {
+ "end-of-stream": "^1.1.0",
+ "once": "^1.3.1"
+ }
+ },
+ "punycode": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
+ "integrity": "sha1-tYsBCsQMIsVldhbI0sLALHv0eew=",
+ "dev": true
+ },
+ "readable-stream": {
+ "version": "2.3.6",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+ "integrity": "sha1-sRwn2IuP8fvgcGQ8+UsMea4bCq8=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "readdirp": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz",
+ "integrity": "sha1-DodiKjMlqjPokihcr4tOhGUppSU=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "graceful-fs": "^4.1.11",
+ "micromatch": "^3.1.10",
+ "readable-stream": "^2.0.2"
+ }
+ },
+ "regenerator-runtime": {
+ "version": "0.13.2",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz",
+ "integrity": "sha1-MuWcmm+5saSv8JtJMMotRHc0NEc="
+ },
+ "regex-not": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz",
+ "integrity": "sha1-H07OJ+ALC2XgJHpoEOaoXYOldSw=",
+ "dev": true,
+ "requires": {
+ "extend-shallow": "^3.0.2",
+ "safe-regex": "^1.1.0"
+ }
+ },
+ "regexpp": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz",
+ "integrity": "sha1-jRnTHPYySCtYkEn4KB+T28uk0H8=",
+ "dev": true
+ },
+ "remove-trailing-separator": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz",
+ "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=",
+ "dev": true,
+ "optional": true
+ },
+ "repeat-element": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz",
+ "integrity": "sha1-eC4NglwMWjuzlzH4Tv7mt0Lmsc4=",
+ "dev": true
+ },
+ "repeat-string": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz",
+ "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=",
+ "dev": true
+ },
+ "require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=",
+ "dev": true
+ },
+ "require-main-filename": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz",
+ "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=",
+ "dev": true
+ },
+ "resolve": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz",
+ "integrity": "sha1-O9qur0XMB/N1ZW39LlTtCBCxAbo=",
+ "dev": true,
+ "requires": {
+ "path-parse": "^1.0.6"
+ }
+ },
+ "resolve-dir": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz",
+ "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=",
+ "dev": true,
+ "requires": {
+ "expand-tilde": "^2.0.0",
+ "global-modules": "^1.0.0"
+ }
+ },
+ "resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha1-SrzYUq0y3Xuqv+m0DgCjbbXzkuY=",
+ "dev": true
+ },
+ "resolve-url": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
+ "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=",
+ "dev": true
+ },
+ "restore-cursor": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz",
+ "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=",
+ "dev": true,
+ "requires": {
+ "onetime": "^2.0.0",
+ "signal-exit": "^3.0.2"
+ }
+ },
+ "ret": {
+ "version": "0.1.15",
+ "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz",
+ "integrity": "sha1-uKSCXVvbH8P29Twrwz+BOIaBx7w=",
+ "dev": true
+ },
+ "rimraf": {
+ "version": "2.6.3",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
+ "integrity": "sha1-stEE/g2Psnz54KHNqCYt04M8bKs=",
+ "dev": true,
+ "requires": {
+ "glob": "^7.1.3"
+ }
+ },
+ "run-async": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz",
+ "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=",
+ "dev": true,
+ "requires": {
+ "is-promise": "^2.1.0"
+ }
+ },
+ "rxjs": {
+ "version": "6.4.0",
+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz",
+ "integrity": "sha1-87sP572n+2nerAwW8XtQsLh5BQQ=",
+ "dev": true,
+ "requires": {
+ "tslib": "^1.9.0"
+ }
+ },
+ "safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha1-mR7GnSluAxN0fVm9/St0XDX4go0=",
+ "dev": true
+ },
+ "safe-regex": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
+ "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=",
+ "dev": true,
+ "requires": {
+ "ret": "~0.1.10"
+ }
+ },
+ "safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha1-RPoWGwGHuVSd2Eu5GAL5vYOFzWo=",
+ "dev": true
+ },
+ "semver": {
+ "version": "5.6.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz",
+ "integrity": "sha1-fnQlb7qknHWqfHogXMInmcrIAAQ=",
+ "dev": true
+ },
+ "set-blocking": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
+ "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
+ "dev": true
+ },
+ "set-value": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz",
+ "integrity": "sha1-ca5KiPD+77v1LR6mBPP7MV67YnQ=",
+ "dev": true,
+ "requires": {
+ "extend-shallow": "^2.0.1",
+ "is-extendable": "^0.1.1",
+ "is-plain-object": "^2.0.3",
+ "split-string": "^3.0.1"
+ },
+ "dependencies": {
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "dev": true,
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ }
+ }
+ },
+ "shebang-command": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
+ "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
+ "dev": true,
+ "requires": {
+ "shebang-regex": "^1.0.0"
+ }
+ },
+ "shebang-regex": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
+ "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=",
+ "dev": true
+ },
+ "shelljs": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.3.0.tgz",
+ "integrity": "sha1-NZbmMHp4FUT1kfN9phg2DzHbV7E=",
+ "dev": true
+ },
+ "signal-exit": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
+ "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=",
+ "dev": true
+ },
+ "slash": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz",
+ "integrity": "sha1-3lUoUaF1nfOo8gZTVEL17E3eq0Q=",
+ "dev": true
+ },
+ "slice-ansi": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz",
+ "integrity": "sha1-ys12k0YaY3pXiNkqfdT7oGjoFjY=",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.0",
+ "astral-regex": "^1.0.0",
+ "is-fullwidth-code-point": "^2.0.0"
+ }
+ },
+ "snapdragon": {
+ "version": "0.8.2",
+ "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz",
+ "integrity": "sha1-ZJIufFZbDhQgS6GqfWlkJ40lGC0=",
+ "dev": true,
+ "requires": {
+ "base": "^0.11.1",
+ "debug": "^2.2.0",
+ "define-property": "^0.2.5",
+ "extend-shallow": "^2.0.1",
+ "map-cache": "^0.2.2",
+ "source-map": "^0.5.6",
+ "source-map-resolve": "^0.5.0",
+ "use": "^3.1.0"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^0.1.0"
+ }
+ },
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "dev": true,
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ }
+ }
+ },
+ "snapdragon-node": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz",
+ "integrity": "sha1-bBdfhv8UvbByRWPo88GwIaKGhTs=",
+ "dev": true,
+ "requires": {
+ "define-property": "^1.0.0",
+ "isobject": "^3.0.0",
+ "snapdragon-util": "^3.0.1"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
+ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^1.0.0"
+ }
+ },
+ "is-accessor-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+ "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-data-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+ "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-descriptor": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+ "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=",
+ "dev": true,
+ "requires": {
+ "is-accessor-descriptor": "^1.0.0",
+ "is-data-descriptor": "^1.0.0",
+ "kind-of": "^6.0.2"
+ }
+ }
+ }
+ },
+ "snapdragon-util": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz",
+ "integrity": "sha1-+VZHlIbyrNeXAGk/b3uAXkWrVuI=",
+ "dev": true,
+ "requires": {
+ "kind-of": "^3.2.0"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "source-map": {
+ "version": "0.5.7",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+ "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
+ "dev": true
+ },
+ "source-map-resolve": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz",
+ "integrity": "sha1-cuLMNAlVQ+Q7LGKyxMENSpBU8lk=",
+ "dev": true,
+ "requires": {
+ "atob": "^2.1.1",
+ "decode-uri-component": "^0.2.0",
+ "resolve-url": "^0.2.1",
+ "source-map-url": "^0.4.0",
+ "urix": "^0.1.0"
+ }
+ },
+ "source-map-support": {
+ "version": "0.5.11",
+ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.11.tgz",
+ "integrity": "sha1-76ws4IADVdAmMmoMoj4WKurJpOI=",
+ "dev": true,
+ "requires": {
+ "buffer-from": "^1.0.0",
+ "source-map": "^0.6.0"
+ },
+ "dependencies": {
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=",
+ "dev": true
+ }
+ }
+ },
+ "source-map-url": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz",
+ "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=",
+ "dev": true
+ },
+ "split-string": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
+ "integrity": "sha1-fLCd2jqGWFcFxks5pkZgOGguj+I=",
+ "dev": true,
+ "requires": {
+ "extend-shallow": "^3.0.0"
+ }
+ },
+ "sprintf-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
+ "dev": true
+ },
+ "static-extend": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz",
+ "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=",
+ "dev": true,
+ "requires": {
+ "define-property": "^0.2.5",
+ "object-copy": "^0.1.0"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^0.1.0"
+ }
+ }
+ }
+ },
+ "string-width": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
+ "integrity": "sha1-q5Pyeo3BPSjKyBXEYhQ6bZASrp4=",
+ "dev": true,
+ "requires": {
+ "is-fullwidth-code-point": "^2.0.0",
+ "strip-ansi": "^4.0.0"
+ }
+ },
+ "string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "safe-buffer": "~5.1.0"
+ }
+ },
+ "strip-ansi": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
+ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^3.0.0"
+ }
+ },
+ "strip-eof": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
+ "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=",
+ "dev": true
+ },
+ "strip-json-comments": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
+ "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
+ "dev": true
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha1-4uaaRKyHcveKHsCzW2id9lMO/I8=",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ },
+ "table": {
+ "version": "5.2.3",
+ "resolved": "https://registry.npmjs.org/table/-/table-5.2.3.tgz",
+ "integrity": "sha1-zeDMbrBnUcAJ76sn6Mggyltnt/I=",
+ "dev": true,
+ "requires": {
+ "ajv": "^6.9.1",
+ "lodash": "^4.17.11",
+ "slice-ansi": "^2.1.0",
+ "string-width": "^3.0.0"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+ "integrity": "sha1-i5+PCM8ay4Q3Vqg5yox+MWjFGZc=",
+ "dev": true
+ },
+ "string-width": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
+ "integrity": "sha1-InZ74htirxCBV0MG9prFG2IgOWE=",
+ "dev": true,
+ "requires": {
+ "emoji-regex": "^7.0.1",
+ "is-fullwidth-code-point": "^2.0.0",
+ "strip-ansi": "^5.1.0"
+ }
+ },
+ "strip-ansi": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+ "integrity": "sha1-jJpTb+tq/JYr36WxBKUJHBrZwK4=",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^4.1.0"
+ }
+ }
+ }
+ },
+ "text-table": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
+ "dev": true
+ },
+ "through": {
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
+ "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
+ "dev": true
+ },
+ "tmp": {
+ "version": "0.0.33",
+ "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
+ "integrity": "sha1-bTQzWIl2jSGyvNoKonfO07G/rfk=",
+ "dev": true,
+ "requires": {
+ "os-tmpdir": "~1.0.2"
+ }
+ },
+ "to-fast-properties": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
+ "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=",
+ "dev": true
+ },
+ "to-object-path": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz",
+ "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=",
+ "dev": true,
+ "requires": {
+ "kind-of": "^3.0.2"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "to-regex": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz",
+ "integrity": "sha1-E8/dmzNlUvMLUfM6iuG0Knp1mc4=",
+ "dev": true,
+ "requires": {
+ "define-property": "^2.0.2",
+ "extend-shallow": "^3.0.2",
+ "regex-not": "^1.0.2",
+ "safe-regex": "^1.1.0"
+ }
+ },
+ "to-regex-range": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz",
+ "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=",
+ "dev": true,
+ "requires": {
+ "is-number": "^3.0.0",
+ "repeat-string": "^1.6.1"
+ }
+ },
+ "trim-right": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz",
+ "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=",
+ "dev": true
+ },
+ "tslib": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz",
+ "integrity": "sha1-1+TdeSRdhUKMTX5IIqeZF5VMooY=",
+ "dev": true
+ },
+ "type-check": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
+ "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=",
+ "dev": true,
+ "requires": {
+ "prelude-ls": "~1.1.2"
+ }
+ },
+ "union-value": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz",
+ "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=",
+ "dev": true,
+ "requires": {
+ "arr-union": "^3.1.0",
+ "get-value": "^2.0.6",
+ "is-extendable": "^0.1.1",
+ "set-value": "^0.4.3"
+ },
+ "dependencies": {
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "dev": true,
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ },
+ "set-value": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz",
+ "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=",
+ "dev": true,
+ "requires": {
+ "extend-shallow": "^2.0.1",
+ "is-extendable": "^0.1.1",
+ "is-plain-object": "^2.0.1",
+ "to-object-path": "^0.3.0"
+ }
+ }
+ }
+ },
+ "unset-value": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz",
+ "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=",
+ "dev": true,
+ "requires": {
+ "has-value": "^0.3.1",
+ "isobject": "^3.0.0"
+ },
+ "dependencies": {
+ "has-value": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz",
+ "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=",
+ "dev": true,
+ "requires": {
+ "get-value": "^2.0.3",
+ "has-values": "^0.1.4",
+ "isobject": "^2.0.0"
+ },
+ "dependencies": {
+ "isobject": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz",
+ "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=",
+ "dev": true,
+ "requires": {
+ "isarray": "1.0.0"
+ }
+ }
+ }
+ },
+ "has-values": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz",
+ "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=",
+ "dev": true
+ }
+ }
+ },
+ "upath": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.2.tgz",
+ "integrity": "sha1-PbZYYA7a7sy+bbXmhNZ+6MKs0Gg=",
+ "dev": true,
+ "optional": true
+ },
+ "uri-js": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
+ "integrity": "sha1-lMVA4f93KVbiKZUHwBCupsiDjrA=",
+ "dev": true,
+ "requires": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "urix": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz",
+ "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=",
+ "dev": true
+ },
+ "use": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
+ "integrity": "sha1-1QyMrHmhn7wg8pEfVuuXP04QBw8=",
+ "dev": true
+ },
+ "util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
+ "dev": true,
+ "optional": true
+ },
+ "uuid": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz",
+ "integrity": "sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho="
+ },
+ "v8flags": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.1.2.tgz",
+ "integrity": "sha1-/FzQwidCgYHmwpspkuT48dpeDJ8=",
+ "dev": true,
+ "requires": {
+ "homedir-polyfill": "^1.0.1"
+ }
+ },
+ "vow": {
+ "version": "0.4.19",
+ "resolved": "https://registry.npmjs.org/vow/-/vow-0.4.19.tgz",
+ "integrity": "sha1-zF701rtpctgwgwp8ns+K2DSnxSU="
+ },
+ "vow-fs": {
+ "version": "0.3.6",
+ "resolved": "https://registry.npmjs.org/vow-fs/-/vow-fs-0.3.6.tgz",
+ "integrity": "sha1-LUxZviLivyYY3fWXq0uqkjvnIA0=",
+ "requires": {
+ "glob": "^7.0.5",
+ "uuid": "^2.0.2",
+ "vow": "^0.4.7",
+ "vow-queue": "^0.4.1"
+ }
+ },
+ "vow-queue": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/vow-queue/-/vow-queue-0.4.3.tgz",
+ "integrity": "sha1-S6j2S1bpISwNvlfxQFruvVTM540=",
+ "requires": {
+ "vow": "^0.4.17"
+ }
+ },
+ "which": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
+ "integrity": "sha1-pFBD1U9YBTFtqNYvn1CRjT2nCwo=",
+ "dev": true,
+ "requires": {
+ "isexe": "^2.0.0"
+ }
+ },
+ "which-module": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
+ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=",
+ "dev": true
+ },
+ "wide-align": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
+ "integrity": "sha1-rgdOa9wMFKQx6ATmJFScYzsABFc=",
+ "dev": true,
+ "requires": {
+ "string-width": "^1.0.2 || 2"
+ }
+ },
+ "wordwrap": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
+ "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=",
+ "dev": true
+ },
+ "wrap-ansi": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
+ "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
+ "dev": true,
+ "requires": {
+ "string-width": "^1.0.1",
+ "strip-ansi": "^3.0.1"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
+ "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
+ "dev": true
+ },
+ "is-fullwidth-code-point": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
+ "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
+ "dev": true,
+ "requires": {
+ "number-is-nan": "^1.0.0"
+ }
+ },
+ "string-width": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
+ "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
+ "dev": true,
+ "requires": {
+ "code-point-at": "^1.0.0",
+ "is-fullwidth-code-point": "^1.0.0",
+ "strip-ansi": "^3.0.0"
+ }
+ },
+ "strip-ansi": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+ "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^2.0.0"
+ }
+ }
+ }
+ },
+ "wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
+ },
+ "write": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz",
+ "integrity": "sha1-CADhRSO5I6OH5BUSPIZWFqrg9cM=",
+ "dev": true,
+ "requires": {
+ "mkdirp": "^0.5.1"
+ }
+ },
+ "y18n": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
+ "integrity": "sha1-le+U+F7MgdAHwmThkKEg8KPIVms=",
+ "dev": true
+ },
+ "yargs": {
+ "version": "12.0.5",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz",
+ "integrity": "sha1-BfWZe2CWR7ZPZrgeO0sQo2jnrRM=",
+ "dev": true,
+ "requires": {
+ "cliui": "^4.0.0",
+ "decamelize": "^1.2.0",
+ "find-up": "^3.0.0",
+ "get-caller-file": "^1.0.1",
+ "os-locale": "^3.0.0",
+ "require-directory": "^2.1.1",
+ "require-main-filename": "^1.0.1",
+ "set-blocking": "^2.0.0",
+ "string-width": "^2.0.0",
+ "which-module": "^2.0.0",
+ "y18n": "^3.2.1 || ^4.0.0",
+ "yargs-parser": "^11.1.1"
+ }
+ },
+ "yargs-parser": {
+ "version": "11.1.1",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz",
+ "integrity": "sha1-h5oIZZc7yp9rq1y987HGfsfTvPQ=",
+ "dev": true,
+ "requires": {
+ "camelcase": "^5.0.0",
+ "decamelize": "^1.2.0"
+ }
+ },
+ "yargs-unparser": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.5.0.tgz",
+ "integrity": "sha1-8rsqfoPLyHu5XI5XKCigbJrdbg0=",
+ "dev": true,
+ "requires": {
+ "flat": "^4.1.0",
+ "lodash": "^4.17.11",
+ "yargs": "^12.0.5"
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
index 75d8ab3a..d0e74e0f 100644
--- a/package.json
+++ b/package.json
@@ -1,61 +1,54 @@
{
- "name": "csscomb",
- "description": "CSS coding style formatter",
- "version": "1.0.0",
- "homepage": "http://csscomb.com/",
- "author": "Mikhail Troshev ",
- "repository": "https://github.com/csscomb/csscomb.js",
- "maintainers": [
- {
- "name": "Mikhail Troshev",
- "email": "mishanga@yandex-team.ru",
- "web": "http://mishanga.pro/"
- },
- {
- "name": "Slava Oliyanchuk",
- "email": "miripiruni@gmail.com",
- "web": "http://miripiruni.org/"
- }
- ],
- "contributors": [
- {
- "name": "Sergey Puzankov",
- "email": "puzankov@yandex-team.ru"
- },
- {
- "name": "Denis Payase",
- "email": "lostsoul@yandex-team.ru"
- },
- {
- "name": "Igor Novak",
- "email": "bezengi@gmail.com"
- },
- {
- "name": "Roman Komarov",
- "email": "kizmarh@ya.ru"
- }
- ],
- "engines": {
- "node": ">= 0.8.0"
- },
- "dependencies": {
- "commander": "2.0.0",
- "gonzales": "1.0.7",
- "minimatch": "0.2.12",
- "vow": "0.3.11",
- "vow-fs": "0.2.3"
- },
- "devDependencies": {
- "jshint-groups": "0.5.3",
- "jshint": "2.3.0",
- "jscs": "1.0.11",
- "mocha": "1.14.0"
- },
- "main": "./lib/csscomb.js",
- "bin": {
- "csscomb": "./bin/csscomb"
- },
- "scripts": {
- "test": "./node_modules/.bin/jshint-groups && ./node_modules/.bin/jscs . && ./node_modules/.bin/mocha -u bdd -R spec"
+ "name": "csscomb",
+ "description": "CSS coding style formatter",
+ "version": "4.3.0",
+ "homepage": "http://csscomb.com/",
+ "author": "Mikhail Troshev ",
+ "repository": "https://github.com/csscomb/csscomb.js",
+ "license": "MIT",
+ "maintainers": [
+ {
+ "name": "Tony Ganch",
+ "email": "tonyganch+github@gmail.com",
+ "web": "http://tonyganch.com/"
}
+ ],
+ "engines": {
+ "node": ">= 6.0.0"
+ },
+ "dependencies": {
+ "@babel/polyfill": "^7.4.0",
+ "gonzales-pe": "4.2.4",
+ "minimatch": "3.0.4",
+ "minimist": "1.2.0",
+ "vow": "0.4.19",
+ "vow-fs": "0.3.6"
+ },
+ "devDependencies": {
+ "@babel/cli": "^7.2.3",
+ "@babel/core": "^7.4.0",
+ "@babel/node": "^7.2.2",
+ "@babel/plugin-transform-destructuring": "^7.4.0",
+ "@babel/plugin-transform-strict-mode": "^7.2.0",
+ "acorn": "^6.1.1",
+ "eslint": "^5.15.3",
+ "jshint": "2.10.2",
+ "mocha": "6.0.2"
+ },
+ "main": "./lib/csscomb.js",
+ "files": [
+ "bin",
+ "config",
+ "lib"
+ ],
+ "bin": {
+ "csscomb": "./bin/csscomb"
+ },
+ "scripts": {
+ "build": "./scripts/build.sh",
+ "coverage": "./scripts/coverage.sh",
+ "prepublish": "./scripts/build.sh",
+ "test": "./scripts/build.sh && ./scripts/test.sh",
+ "watch": "./scripts/watch.sh"
+ }
}
diff --git a/scripts/build.sh b/scripts/build.sh
new file mode 100755
index 00000000..bbfd5f36
--- /dev/null
+++ b/scripts/build.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+printf "\n\
+-----------------------\n\
+ Building source files\n\
+-----------------------\n\n"
+./node_modules/.bin/babel --plugins @babel/plugin-transform-destructuring --loose all src --out-dir lib
diff --git a/scripts/coverage.sh b/scripts/coverage.sh
new file mode 100755
index 00000000..d0200e61
--- /dev/null
+++ b/scripts/coverage.sh
@@ -0,0 +1 @@
+rm -rf lib-cov && jscoverage lib lib-cov && TEST_COV=true node test/mocha > ./test/test-coverage.html
diff --git a/scripts/test.sh b/scripts/test.sh
new file mode 100755
index 00000000..1948c810
--- /dev/null
+++ b/scripts/test.sh
@@ -0,0 +1,38 @@
+#!/bin/bash
+
+EXIT_CODE=0
+
+function test {
+ "$@"
+ if [ $? -ne 0 ]; then
+ EXIT_CODE=1
+ fi
+}
+
+# Run linters
+printf "\n\
+----------------\n\
+ Running JSHint\n\
+----------------\n\n"
+test ./node_modules/.bin/jshint ./src
+
+# Run tests
+printf "\n\
+---------------\n\
+ Running Mocha\n\
+---------------\n\n"
+test ./node_modules/.bin/babel-node --plugins @babel/plugin-transform-strict-mode ./test/mocha
+
+if [ $EXIT_CODE -ne 0 ]; then
+printf "\n\
+----------------------------------------------------\n\
+ Please, fix errors shown above and run tests again\n\
+----------------------------------------------------\n"
+else
+printf "\n\
+------------------------\n\
+ Everything looks fine!\n\
+------------------------\n"
+fi
+
+exit $EXIT_CODE
diff --git a/scripts/watch.sh b/scripts/watch.sh
new file mode 100755
index 00000000..2a237f9e
--- /dev/null
+++ b/scripts/watch.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+printf "\n\
+-----------------------\n\
+ Watching source files\n\
+-----------------------\n\n"
+
+./node_modules/.bin/babel --loose all --watch src --out-dir lib
diff --git a/src/cli.js b/src/cli.js
new file mode 100644
index 00000000..5965dd9b
--- /dev/null
+++ b/src/cli.js
@@ -0,0 +1,192 @@
+'use strict';
+
+/**
+ * Command line implementation for CSSComb
+ *
+ * Usage example:
+ * ./node_modules/.bin/csscomb [options] [file1 [dir1 [fileN [dirN]]]]
+ */
+var fs = require('fs');
+var parseArgs = require('minimist');
+var path = require('path');
+var os = require('os');
+
+var Comb = require('./csscomb');
+var Errors = require('./errors');
+
+
+var getInputData = new Promise(function(resolve) {
+ var input = '';
+ process.stdin.resume();
+ process.stdin.setEncoding('utf8');
+ process.stdin.on('data', function(data) {
+ input += data;
+ });
+ process.stdin.on('end', function() {
+ resolve(input);
+ });
+});
+
+var comb = new Comb();
+var options = getOptions();
+
+if (options.help) {
+ displayHelp();
+ process.exit(0);
+}
+
+if (options.detect) {
+ const config = detectConfig();
+ process.stdout.write(config);
+ process.exit(0);
+}
+
+var config = getConfig();
+comb.configure(config);
+
+if (options['tty-mode'] || process.stdin.isTTY) {
+ processFiles(options._);
+} else {
+ processSTDIN();
+}
+
+
+function getOptions() {
+ var parserOptions = {
+ boolean: ['help', 'lint', 'verbose', 'tty-mode'],
+ alias: {
+ config: 'c',
+ detect: 'd',
+ lint: 'l',
+ help: 'h',
+ verbose: 'v',
+ 'tty-mode': 't'
+ }
+ };
+ return parseArgs(process.argv.slice(2), parserOptions);
+}
+
+function displayHelp() {
+ var help = [
+ 'NAME',
+ ' csscomb — Lint and fix style errors in css files',
+ '',
+ 'SYNOPSIS',
+ ' csscomb [options] file.css',
+ ' cat file.css | csscomb [options] -',
+ '',
+ 'OPTIONS',
+ ' -c, --config [path]',
+ ' Path to configuration file.',
+ ' -d, --detect',
+ ' Run the tool in detect mode, returning detected options.',
+ ' -l, --lint',
+ ' Run the tool in linter mode, without modifying files.',
+ ' -h, --help',
+ ' Display help message.',
+ ' -v, --verbose',
+ ' Whether to print logging info.',
+ ' -t, --tty-mode',
+ ' Run the tool in TTY mode using external app (e.g. IDE).',
+ ''
+ ];
+ process.stdout.write(help.join(os.EOL));
+}
+
+function detectConfig() {
+ const config = Comb.detectInFile(options.detect);
+ return JSON.stringify(config, false, 4);
+}
+
+function getConfig() {
+ var configPath = options.config &&
+ path.resolve(process.cwd(), options.config) ||
+ Comb.getCustomConfigPath();
+
+ var config;
+ if (!fs.existsSync(configPath)) {
+ config = require('../config/csscomb.json');
+ } else if (configPath.match(/\.css$/)) {
+ config = Comb.detectInFile(configPath);
+ } else {
+ config = Comb.getCustomConfig(configPath);
+ }
+
+ if (!config) {
+ const errorMessage = Errors.configParsingError(configPath);
+ process.stderr.write(errorMessage);
+ process.exit(1);
+ }
+
+ applyTemplate(config);
+ if (options.verbose) config.verbose = options.verbose;
+ if (options.lint) config.lint = options.lint;
+
+ return config;
+}
+
+function applyTemplate(config) {
+ if (!config.template) return;
+
+ if (!fs.existsSync(config.template)) {
+ const errorMessage = Errors.missingTemplateFile(config.template);
+ process.stderr.write(errorMessage);
+ process.exit(1);
+ }
+
+ var templateConfig = Comb.detectInFile(config.template);
+ for (var attrname in templateConfig) {
+ if (templateConfig.hasOwnProperty(attrname) && !config[attrname]) {
+ config[attrname] = templateConfig[attrname];
+ }
+ }
+}
+
+function processFiles(files) {
+ const promises = files.map(file => {
+ return comb.processPath(file);
+ });
+
+ Promise.all(promises).catch(error => {
+ process.stderr.write(error.stack);
+ process.exit(1);
+ }).then(c => {
+ c = [].concat.apply([], c);
+ var tbchanged = c.filter(isChanged => {
+ return isChanged !== undefined;
+ }).reduce((a, b) => {
+ return a + b;
+ }, 0);
+
+ var changed = options.lint ? 0 : tbchanged;
+
+ if (options.verbose) {
+ let message = [
+ `${c.length} file${c.length === 1 ? '' : 's'} processed`,
+ `${changed} file${changed === 1 ? '' : 's'} fixed`,
+ ''
+ ].join(os.EOL);
+ process.stdout.write(message);
+ }
+
+ if (options.lint && tbchanged) {
+ process.exit(1);
+ }
+
+ process.exit(0);
+ });
+}
+
+function processSTDIN() {
+ getInputData.then(processInputData);
+}
+
+function processInputData(input) {
+ comb.processString(input).catch(e => {
+ process.stderr.write(e.message);
+ process.exit(1);
+ }).then(output => {
+ process.stdout.write(output);
+ process.exit(0);
+ });
+}
diff --git a/src/core.js b/src/core.js
new file mode 100644
index 00000000..eea63d2f
--- /dev/null
+++ b/src/core.js
@@ -0,0 +1,470 @@
+'use strict';
+
+if (!global._babelPolyfill)
+ require('@babel/polyfill');
+
+let fs = require('fs');
+var os = require('os');
+let gonzales = require('gonzales-pe');
+let minimatch = require('minimatch');
+let Errors = require('./errors');
+let Plugin = require('./plugin');
+
+let vow = require('vow');
+let vfs = require('vow-fs');
+
+class Comb {
+ constructor() {
+ this.config = {};
+ this.exclude = [];
+ // Whether lint mode is on.
+ this.lint = false;
+ // List of file paths that should be excluded from processing.
+ this.pathsToExclude = null;
+ // List of used plugins.
+ this.plugins = [];
+ this.pluginsDependencies = {};
+ // List of supported syntaxes.
+ this.supportedSyntaxes = new Set();
+ // Mapping file extensions to syntax
+ this.syntaxMap = new Map();
+ // Whether verbose mode is on.
+ this.verbose = false;
+ }
+
+ /**
+ * Loads configuration from JSON.
+ *
+ * @param {!Object} config
+ * @return {!Comb}
+ */
+ configure(config) {
+ if (typeof config !== 'object')
+ // TODO: throw error
+ throw new Error();
+
+ this.lint = config.lint;
+ this.verbose = config.verbose;
+ if (config.exclude)
+ this.exclude = config.exclude.map(function(pattern) {
+ return new minimatch.Minimatch(pattern);
+ });
+
+ if (config.syntax) {
+ for (let key in config.syntax) {
+ this.syntaxMap.set(key, config.syntax[key]);
+ }
+ }
+
+ for (let i = 0, l = this.plugins.length; i < l; i++) {
+ let plugin = this.plugins[i];
+ let name = plugin.name;
+ if (!config.hasOwnProperty(name)) continue;
+
+ try {
+ plugin.value = config[name];
+ this.config[name] = plugin.value;
+ } catch (e) {
+ // TODO: throw error
+ }
+ }
+
+ // Chaining.
+ return this;
+ }
+
+ /**
+ * Lints all files in a directory.
+ *
+ * @param {String} path
+ * @returns {Promise}
+ */
+ lintDirectory(path) {
+ let files = this._getAcceptableFilesFromDirectory(path);
+ let promises = files.map((file) => this.lintFile(file));
+ return Promise.all(promises);
+ }
+
+ /**
+ * Lints a single file.
+ *
+ * @param {String} path
+ * @returns {Promise}
+ */
+ lintFile(path) {
+ let syntax = this._extractSyntax(path);
+ return this._readFile(path).then((string) => {
+ return this.lintString(string, {syntax: syntax, filename: path});
+ });
+ }
+
+ /**
+ * Lints a file or a directory.
+ *
+ * @param {String} path
+ */
+ lintPath(path) {
+ path = path.replace(/\/$/, '');
+ return fs.statSync(path).isDirectory() ?
+ this.lintDirectory(path) :
+ this.lintFile(path);
+ }
+
+ /**
+ * Lints a string.
+ *
+ * @param {String} text
+ * @param {{context: String, filename: String, syntax: String}} options
+ * @returns {Promise} Resolves with list of found errors.
+ */
+ lintString(text, options) {
+ return this._parseString(text, options)
+ .then(this._lintTree.bind(this))
+ .then(errors => {
+ errors.forEach(error => {
+ error.context = this._getContext(text, error.line);
+ });
+
+ return errors;
+ });
+ }
+
+ _getContext(text, currentLineNumber) {
+ var LINES_AROUND = 2;
+ var result = [];
+ var start = currentLineNumber - 1 - LINES_AROUND;
+ var end = currentLineNumber + LINES_AROUND;
+ var lines = text.split(/\r\n|\r|\n/);
+
+ for (var i = start; i < end; i++) {
+ var line = lines[i];
+ if (!line) continue;
+ var ln = i + 1;
+ if (ln === currentLineNumber) {
+ result.push(ln + '*| ' + line);
+ } else {
+ result.push(ln + ' | ' + line);
+ }
+ }
+ return result.join(os.EOL);
+ }
+
+ /**
+ * Processes directory recursively.
+ *
+ * @param {String} path
+ * @returns {Promise}
+ */
+ processDirectory(path) {
+ let that = this;
+
+ return vfs.listDir(path).then(function(filenames) {
+ return vow.all(filenames.map(function(filename) {
+ let fullname = path + '/' + filename;
+ return vfs.stat(fullname).then(function(stat) {
+ if (stat.isDirectory() && that._shouldProcess(fullname)) {
+ return that.processDirectory(fullname);
+ } else {
+ return that.processFile(fullname);
+ }
+ });
+ })).then(function(results) {
+ return [].concat.apply([], results);
+ });
+ });
+ }
+
+ /**
+ * Processes single file.
+ *
+ * @param {String} path
+ * @returns {Promise}
+ */
+ processFile(path) {
+ let that = this;
+
+ if (!this._shouldProcessFile(path)) return;
+
+ return new Promise(resolve => {
+ vfs.read(path, 'utf8').then(function(data) {
+ let syntax = that._extractSyntax(path);
+ that.processString(data, {
+ syntax: syntax,
+ filename: path
+ }).then(function(processedData) {
+ if (data === processedData) {
+ if (that.verbose) console.log(' ', path);
+ resolve(0);
+ return;
+ }
+
+ let tick = that.lint ? '!' : '✓';
+ if (that.lint) {
+ if (that.verbose) console.log(tick, path);
+ resolve(1);
+ } else {
+ return vfs.write(path, processedData, 'utf8').then(function() {
+ if (that.verbose) console.log(tick, path);
+ resolve(1);
+ });
+ }
+ });
+ });
+ });
+ }
+
+ /**
+ * Processes directory or file.
+ *
+ * @returns {Promise}
+ */
+ processPath(path) {
+ let that = this;
+ path = path.replace(/\/$/, '');
+
+ return vfs.stat(path).then(function(stat) {
+ if (stat.isDirectory()) {
+ return that.processDirectory(path);
+ } else {
+ return that.processFile(path);
+ }
+ });
+ }
+
+ /**
+ * Processes a string.
+ *
+ * @param {String} text
+ * @param {{context: String, filename: String, syntax: String}} options
+ * @returns {Promise} Resolves in processed string
+ */
+ processString(text, options) {
+ return this._parseString(text, options)
+ .then(this._processTree.bind(this))
+ .then((ast) => ast.toString());
+ }
+
+ /**
+ * Add a plugin.
+ * @param {Object} options
+ * @return {Comb}
+ */
+ use(options) {
+ // Check whether plugin with the same is already used.
+ let pluginName = options.name;
+ if (this._pluginAlreadyUsed(pluginName)) {
+ if (this.verbose)
+ console.warn(Errors.twoPluginsWithSameName(pluginName));
+ return;
+ }
+
+ let plugin = new Plugin(options);
+
+ plugin.syntax.forEach(function(s) {
+ this.supportedSyntaxes.add(s);
+ }, this);
+
+ // Sort plugins.
+ let pluginToRunBefore = plugin.runBefore;
+
+ if (!pluginToRunBefore) {
+ this.plugins.push(plugin);
+ } else {
+ if (this._pluginAlreadyUsed(pluginToRunBefore)) {
+ let i = this._pluginIndex(pluginToRunBefore);
+ this.plugins.splice(i, 0, plugin);
+ } else {
+ this.plugins.push(plugin);
+ if (!this.pluginsDependencies[pluginToRunBefore])
+ this.pluginsDependencies[pluginToRunBefore] = [];
+ this.pluginsDependencies[pluginToRunBefore].push(pluginName);
+ }
+ }
+
+ let dependents = this.pluginsDependencies[pluginName];
+ if (!dependents) return this;
+
+ for (let i = 0, l = dependents.length; i < l; i++) {
+ let name = dependents[i];
+ let x = this._pluginIndex(name);
+ let plugin = this.plugins[x];
+ this.plugins.splice(x, 1);
+ this.plugins.splice(-1, 0, plugin);
+ }
+
+ // Chaining.
+ return this;
+ }
+
+ _getAcceptableFilesFromDirectory(path) {
+ if (!this._shouldProcess(path)) return;
+
+ let files = [];
+ let filesInThisDir = fs.readdirSync(path);
+
+ for (let i = 0, fl = filesInThisDir.length; i < fl; i++) {
+ let fullname = path + '/' + filesInThisDir[i];
+ let stat = fs.statSync(fullname);
+ if (stat.isDirectory() && this._shouldProcess(fullname))
+ files = files.concat(this._getAcceptableFilesFromDirectory(fullname));
+ else if (this._shouldProcessFile(fullname))
+ files.push(fullname);
+ }
+
+ return files;
+ }
+
+ /**
+ * @param {Node} ast
+ * @param {String=} filename
+ * @return {Array} List of errors.
+ */
+ _lintTree(ast, filename) {
+ let errors = [];
+ let config = this.config;
+
+ this.plugins.filter(function(plugin) {
+ return typeof plugin.value !== null &&
+ typeof plugin.lint === 'function' &&
+ plugin.syntax.indexOf(ast.syntax) !== -1;
+ }).forEach(function(plugin) {
+ let e = plugin.lint(ast, config);
+ errors = errors.concat(e);
+ });
+
+ if (filename) {
+ errors.map(function(error) {
+ error.filename = filename;
+ return error;
+ });
+ }
+
+ return errors;
+ }
+
+ _parseString(text, options) {
+ let syntax = options && options.syntax;
+ let filename = options && options.filename || '';
+ let context = options && options.context;
+ let tree;
+ const lint = this.lint;
+
+ if (!text) return new Promise(function(resolve) {
+ resolve(lint ? [] : text);
+ });
+
+ if (!syntax) syntax = 'css';
+ this.syntax = syntax;
+
+ return new Promise(function(resolve) {
+ try {
+ tree = gonzales.parse(text, {syntax: syntax, rule: context});
+ resolve(tree, filename);
+ } catch (e) {
+ let version = require('../package.json').version;
+ let message = filename ? [filename] : [];
+ message.push(e.message);
+ message.push('CSScomb Core version: ' + version);
+ e.stack = e.message = message.join(os.EOL);
+ throw e;
+ }
+ });
+ }
+
+ _pluginAlreadyUsed(name) {
+ return this._pluginIndex(name) !== -1;
+ }
+
+ _pluginIndex(name) {
+ let index = -1;
+ this.plugins.some(function(plugin, i) {
+ if (plugin.name === name) {
+ index = i;
+ return true;
+ }
+ });
+ return index;
+ }
+
+ /**
+ * @param {Node} ast
+ * @return {Node} Transformed AST
+ */
+ _processTree(ast) {
+ let config = this.config;
+
+ this.plugins.filter(function(plugin) {
+ return plugin.value !== null &&
+ typeof plugin.process === 'function' &&
+ plugin.syntax.indexOf(ast.syntax) !== -1;
+ }).forEach(function(plugin) {
+ plugin.process(ast, config);
+ });
+
+ return ast;
+ }
+
+ _readFile(path) {
+ return new Promise((resolve, reject) => {
+ if (!this._shouldProcessFile(path)) reject();
+
+ fs.readFile(path, 'utf8', function(e, string) {
+ if (e) reject();
+ resolve(string);
+ });
+ });
+ }
+
+ /**
+ * Checks if path is not present in `exclude` list.
+ *
+ * @param {String} path
+ * @returns {Boolean} False if specified path is present in `exclude` list.
+ * Otherwise returns true.
+ */
+ _shouldProcess(path) {
+ path = path.replace(/\/$/, '');
+ if (!fs.existsSync(path)) {
+ console.warn('Path ' + path + ' was not found.');
+ return false;
+ }
+
+ path = path.replace(/^\.\//, '');
+ return this.exclude.every(function(e) {
+ return !e.match(path);
+ });
+ }
+
+ /**
+ * Checks if specified path is not present in `exclude` list and it has one of
+ * acceptable syntaxes.
+ *
+ * @param {String} path
+ * @returns {Boolean} False if the path either has unacceptable extension or
+ * is present in `exclude` list. True if everything is ok.
+ */
+ _shouldProcessFile(path) {
+ // Get file's extension:
+ var syntax = this._extractSyntax(path);
+
+ // Check if syntax is supported. If not, ignore the file:
+ if (!this.supportedSyntaxes.has(syntax))
+ return false;
+
+ return this._shouldProcess(path);
+ }
+
+ /**
+ * Extract syntax by file path
+ *
+ * @param {String} path
+ * @returns {String} syntax
+ */
+ _extractSyntax(path) {
+ var extension = path.split('.').pop();
+
+ return this.syntaxMap.get('.' + extension) || extension;
+ }
+}
+
+module.exports = Comb;
diff --git a/src/csscomb.js b/src/csscomb.js
new file mode 100644
index 00000000..a698767a
--- /dev/null
+++ b/src/csscomb.js
@@ -0,0 +1,314 @@
+'use strict';
+
+let gonzales = require('gonzales-pe');
+let fs = require('fs');
+let Comb = require('./core');
+let format = require('./format');
+let path = require('path');
+
+/**
+ * @param {String|Object} config
+ * @constructor
+ * @name CSScomb
+ */
+let CSScomb = function(config) {
+ let comb = new Comb();
+
+ // Add plugins.
+ fs.readdirSync(__dirname + '/options').map(function(option) {
+ return require('./options/' + option);
+ }).forEach(function(option) {
+ comb.use(option);
+ });
+
+ // If config was passed, configure:
+ if (typeof config === 'string') {
+ config = CSScomb.getConfig(config);
+ }
+ if (typeof config === 'object') {
+ comb.configure(config);
+ }
+
+ // Chaining.
+ return comb;
+};
+
+/**
+ * STATIC METHODS
+ * Methods that can be called without creating an instance:
+ * - detectInFile;
+ * - detectInString;
+ * - getConfig;
+ * - getCustomConfig;
+ * - getCustomConfigPath;
+ * For example: `CSScomb.getConfig('zen')`
+ */
+
+/**
+ * Detects the options in the given file
+ *
+ * @param {String} file Path to the stylesheet
+ * @param {Array} options List of options to detect
+ * @returns {Object} Detected options
+ */
+CSScomb.detectInFile = function detectInFile(file, options) {
+ var stylesheet = fs.readFileSync(file, 'utf8');
+ return CSScomb.detectInString(stylesheet, options);
+};
+
+/**
+ * Detects the options in the given string
+ *
+ * @param {String} text Stylesheet
+ * @param {Array} options List of options to detect
+ * @returns {Object} Detected options
+ */
+CSScomb.detectInString = function detectInString(text, options) {
+ var result;
+ var handlers = [];
+
+ if (!text) return text;
+
+ var optionNames = fs.readdirSync(__dirname + '/options');
+ optionNames.forEach(function(option) {
+ option = option.slice(0, -3);
+ if (options && options.indexOf(option) < 0) return;
+ try {
+ handlers.push(getHandler(option));
+ } catch (e) {
+ let message = `\nFailed to load "${option}" option:\n${e.message}`;
+ console.warn(message);
+ }
+ });
+
+ var tree = cssToAST(text);
+ var detectedOptions = detectInTree(tree, handlers);
+ result = getDetectedOptions(detectedOptions, handlers);
+
+ // Handle conflicting options with spaces around braces:
+ var blockIndent = result['block-indent'];
+ var spaceAfterOpeningBrace = result['space-after-opening-brace'];
+
+ if (typeof blockIndent === 'string' &&
+ spaceAfterOpeningBrace &&
+ spaceAfterOpeningBrace.indexOf('\n') > -1) {
+ result['space-after-opening-brace'] = '\n';
+ }
+
+ return result;
+};
+
+/**
+ * Gets one of configuration files from configs' directory.
+ *
+ * @param {String} name Config's name, e.g. 'yandex'
+ * @returns {Object} Configuration object
+ */
+CSScomb.getConfig = function getConfig(name) {
+ const DEFAULT_CONFIG_NAME = 'csscomb';
+ name = name || DEFAULT_CONFIG_NAME;
+
+ if (typeof name !== 'string') {
+ throw new Error('Config name must be a string.');
+ }
+
+ let CONFIG_DIR_PATH = '../config';
+ let dir = `${__dirname}/${CONFIG_DIR_PATH}`;
+ let availableConfigsNames = fs.readdirSync(dir)
+ .map(function(configFileName) {
+ return configFileName.split('.')[0]; // Strip file extension(s)
+ });
+
+ if (availableConfigsNames.indexOf(name) < 0) {
+ let configsNamesAsString = availableConfigsNames
+ .map(function(configName) {
+ return '\'' + configName + '\'';
+ })
+ .join(', ');
+ let message = `"${name}" is not a valid config name. Try one
+ of the following: ${configsNamesAsString}.`;
+ throw new Error(format(message));
+ }
+
+ return require(CONFIG_DIR_PATH + '/' + name + '.json');
+};
+
+/**
+ * Gets configuration from provided config path or looks for it in common
+ * places.
+ *
+ * @param {String} [configPath]
+ * @returns {Object|null}
+ */
+CSScomb.getCustomConfig = function getCustomConfig(configPath) {
+ var config;
+ configPath = configPath || CSScomb.getCustomConfigPath();
+
+ try {
+ config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
+ } catch (e) {
+ config = null;
+ }
+
+ return config;
+};
+
+/**
+ * Looks for a config file: recursively from current (process) directory
+ * up to $HOME dir
+ * If no custom config file is found, return `null`.
+ *
+ * @param {String} [configPath]
+ * @returns {String | null}
+ */
+CSScomb.getCustomConfigPath = function getCustomConfigPath(configPath) {
+ var HOME = process.env.HOME ||
+ process.env.HOMEPATH ||
+ process.env.USERPROFILE;
+
+ configPath = configPath || path.join(process.cwd(), '.csscomb.json');
+
+ // If we've finally found a config, return its path:
+ if (fs.existsSync(configPath)) return fs.realpathSync(configPath);
+
+ // If we are in HOME dir already and yet no config file, return a default
+ // one from our package.
+ // If project is located not under HOME, compare to root instead.
+ // Since there appears to be no good way to get root path in
+ // Windows, assume that if current dir has no parent dir, we're in
+ // root.
+ var dirname = path.dirname(configPath);
+ var parentDirname = path.dirname(dirname);
+ if (dirname === HOME || dirname === parentDirname) return null;
+
+ // If there is no config in this directory, go one level up and look for
+ // a config there:
+ configPath = path.join(parentDirname, '.csscomb.json');
+ return CSScomb.getCustomConfigPath(configPath);
+};
+
+/**
+ * Converts CSS string to AST.
+ *
+ * @param {String} text CSS string
+ * @param {String} [syntax] Syntax name (e.g., `scss`)
+ * @param {String} [filename]
+ * @returns {Array} AST
+ */
+function cssToAST(text, syntax, filename) {
+ var string = JSON.stringify;
+ var fileInfo = filename ? ' at ' + filename : '';
+ var tree;
+
+ try {
+ tree = gonzales.parse(text, {syntax: syntax});
+ } catch (e) {
+ throw new Error('Parsing error' + fileInfo + ': ' + e.message);
+ }
+
+ // TODO: When can tree be undefined?
+ if (typeof tree === 'undefined') {
+ let message = `Undefined tree ${fileInfo}: ${string(text)} =>
+ ${string(tree)}`;
+ throw new Error(format(message));
+ }
+
+ return tree;
+}
+
+/**
+ * Processes tree and detects options.
+ *
+ * @param {Array} tree
+ * @param {Array} handlers List of options that we should look for
+ * @returns {Object} Map with detected options and all variants of possible
+ * values
+ */
+function detectInTree(tree, handlers) {
+ var detectedOptions = {};
+ // We walk across complete tree for each handler,
+ // because we need strictly maintain order in which handlers work,
+ // despite fact that handlers work on different level of the tree.
+ handlers.forEach(function(handler) {
+ detectedOptions[handler.name] = handler.detect(tree);
+ });
+ return detectedOptions;
+}
+
+/**
+ * Gets the detected options.
+ *
+ * @param {Object} detected
+ * @param {Array} handlers
+ * @returns {Object}
+ */
+function getDetectedOptions(detected, handlers) {
+ var options = {};
+ Object.keys(detected).forEach(function(option) {
+ // List of all the detected variants from the stylesheet
+ // for the given option:
+ var values = detected[option];
+ var i;
+ if (!values.length) {
+ // If there are no values for the option, check if there is
+ // a default one:
+ for (i = handlers.length; i--;) {
+ if (handlers[i].name === option &&
+ handlers[i].detectDefault !== undefined) {
+ options[option] = handlers[i].detectDefault;
+ break;
+ }
+ }
+ } else if (values.length === 1) {
+ options[option] = values[0];
+ } else {
+ // If there are more than one value for the option, find
+ // the most popular one; `variants` would be populated
+ // with the popularity for different values.
+ var variants = {};
+ var bestGuess = null;
+ var maximum = 0;
+ for (i = values.length; i--;) {
+ var currentValue = values[i];
+ // Count the current value:
+ if (variants[currentValue]) {
+ variants[currentValue]++;
+ } else {
+ variants[currentValue] = 1;
+ }
+ // If the current variant is the most popular one, treat
+ // it as the best guess:
+ if (variants[currentValue] >= maximum) {
+ maximum = variants[currentValue];
+ bestGuess = currentValue;
+ }
+ }
+ if (bestGuess !== null) {
+ options[option] = bestGuess;
+ }
+ }
+ });
+
+ return options;
+}
+
+/**
+ * Gets option's data needed for detection
+ *
+ * @param {String} optionName
+ * @returns {Object} Object with option's name, link to `detect()` method
+ * and default value for the case when nothing can be detected
+ */
+function getHandler(optionName) {
+ var option = require('./options/' + optionName);
+ if (!option.detect)
+ throw new Error('Option does not have `detect()` method.');
+
+ return {
+ name: option.name,
+ detect: option.detect,
+ detectDefault: option.detectDefault
+ };
+}
+
+module.exports = CSScomb;
diff --git a/src/errors.js b/src/errors.js
new file mode 100644
index 00000000..5dc2e4e5
--- /dev/null
+++ b/src/errors.js
@@ -0,0 +1,71 @@
+'use strict';
+
+
+let format = require('./format');
+
+module.exports = {
+ configParsingError(configPath) {
+ return `Error parsing configuration file ${configPath}.`;
+ },
+
+ implementSetValue(valueType) {
+ if (typeof valueType === 'undefined') throw new Error();
+
+ return format(`If you see this message and you are not
+ a developer adding a new option, please open an issue here:
+ https://github.com/csscomb/core/issues/new\n
+ For option to accept values of type "${valueType}"
+ you need to implement custom \`setValue()\` method.`);
+ },
+
+ missingName() {
+ return 'Plugin must have a valid \`name\` property.';
+ },
+
+ missingSetValue() {
+ return format(`Plugin must either implemet \`setValue()\` method
+ or provide \`accepts\` object with acceptable values.`);
+ },
+
+ missingSyntax() {
+ return 'Plugin must list supported syntaxes.';
+ },
+
+ missingTemplateFile(file) {
+ return format(`Template configuration file ${file}
+ was not found.`);
+ },
+
+ twoPluginsWithSameName(pluginName) {
+ if (typeof pluginName === 'undefined') throw new Error();
+
+ return format(`You're trying to use one plugin twice:
+ ${pluginName}. Please make sure there are not two different
+ plugins with the same name.`);
+ },
+
+ unacceptableBoolean(pattern) {
+ if (typeof pattern === 'undefined') throw new Error();
+
+ return `Value must be one of the following: ${pattern.join(', ')}.`;
+ },
+
+ unacceptableNumber() {
+ return 'Value must be an integer.';
+ },
+
+ unacceptableString(pattern) {
+ if (typeof pattern === 'undefined') throw new Error();
+
+ return `Value must match pattern ${pattern}.`;
+ },
+
+ unacceptableValueType(valueType, accepts) {
+ if (typeof valueType === 'undefined' ||
+ typeof accepts === 'undefined') throw new Error();
+
+ return format(`The option does not accept values of type
+ ${valueType}.\nValue\'s type must be one the following:
+ ${Object.keys(accepts).join(', ')}.`);
+ }
+};
diff --git a/src/format.js b/src/format.js
new file mode 100644
index 00000000..261df4c4
--- /dev/null
+++ b/src/format.js
@@ -0,0 +1,5 @@
+'use strict';
+
+module.exports = function(string) {
+ return string.replace(/\n\s+/gm, ' ');
+};
diff --git a/src/options/always-semicolon.js b/src/options/always-semicolon.js
new file mode 100644
index 00000000..32628216
--- /dev/null
+++ b/src/options/always-semicolon.js
@@ -0,0 +1,147 @@
+'use strict';
+
+var gonzales = require('gonzales-pe');
+
+let option = {
+ /**
+ * Option's name as it's used in config.
+ * @type {String}
+ */
+ get name() {
+ return 'always-semicolon';
+ },
+
+ /**
+ * List of syntaxes that are supported by this option.
+ * @type {Array}
+ */
+ get syntax() {
+ return ['css', 'less', 'sass', 'scss'];
+ },
+
+ /**
+ * Types of values this option accepts in config.
+ * @type {Object}
+ */
+ get accepts() {
+ return {
+ boolean: [true]
+ };
+ },
+
+ /**
+ * Checks ast for code style errors.
+ *
+ * @param {Node} ast
+ * @return {Array?} List of found errors.
+ */
+ lint(ast) {
+ var errors = [];
+
+ ast.traverseByType('block', (block) => {
+ block.eachFor((currentNode) => {
+ var nodeWithoutSemicolon;
+ // Skip nodes that already have `;` at the end:
+ if (currentNode.is('declarationDelimiter')) return null;
+
+ // Add semicolon only after declarations and includes.
+ // If current node is include, insert semicolon right into it.
+ // If it's declaration, look for value node:
+ if (currentNode.is('include') ||
+ currentNode.is('extend')) {
+ nodeWithoutSemicolon = currentNode;
+ } else if (currentNode.is('declaration')) {
+ nodeWithoutSemicolon = currentNode.last('value');
+ } else {
+ return;
+ }
+
+ errors.push({
+ message: 'Missing semicolon',
+ line: nodeWithoutSemicolon.end.line,
+ column: nodeWithoutSemicolon.end.column + 1
+ });
+
+ // Stop looping through block's children:
+ return null;
+ });
+ });
+
+ return errors;
+ },
+
+ /**
+ * Processes ast and fixes found code style errors.
+ * @param {Node} ast
+ */
+ process(ast) {
+ var nodeWithoutSemicolon;
+
+ ast.traverseByType('block', (block) => {
+ block.eachFor((currentNode) => {
+ // Skip nodes that already have `;` at the end:
+ if (currentNode.is('declarationDelimiter')) return null;
+
+ // Add semicolon only after declarations and includes.
+ // If current node is include, insert semicolon right into it.
+ // If it's declaration, look for value node:
+ if (currentNode.is('include') ||
+ currentNode.is('extend')) {
+ nodeWithoutSemicolon = currentNode;
+ } else if (currentNode.is('declaration')) {
+ nodeWithoutSemicolon = currentNode.last('value');
+ } else {
+ return;
+ }
+
+ // Check if there are spaces and comments at the end of the node
+ for (var j = nodeWithoutSemicolon.length; j--;) {
+ var lastNode = nodeWithoutSemicolon.get(j);
+
+ // If the node's last child is block, do not add semicolon:
+ // TODO: Add syntax check and run the code only for scss
+ if (lastNode.is('block')) {
+ return null;
+ } else if (!lastNode.is('space') &&
+ !lastNode.is('multilineComment') &&
+ !lastNode.is('singlelineComment')) {
+ j++;
+ break;
+ }
+ }
+
+ var declDelim = gonzales.createNode({
+ type: 'declarationDelimiter',
+ content: ';'
+ });
+ nodeWithoutSemicolon.insert(j, declDelim);
+ return null;
+ });
+ });
+ },
+
+ /**
+ * Detects the value of this option in ast.
+ * @param {Node} ast
+ * @return {Array?} List of detected values
+ */
+ detect(ast) {
+ var detected = [];
+
+ ast.traverseByType('block', (block) => {
+ block.eachFor((node) => {
+ if (node.is('declarationDelimiter')) {
+ detected.push(true);
+ return null;
+ } else if (node.is('declaration')) {
+ detected.push(false);
+ return null;
+ }
+ });
+ });
+
+ return detected;
+ }
+};
+
+module.exports = option;
diff --git a/src/options/block-indent.js b/src/options/block-indent.js
new file mode 100644
index 00000000..245211e5
--- /dev/null
+++ b/src/options/block-indent.js
@@ -0,0 +1,142 @@
+'use strict';
+
+let option = {
+ /**
+ * Option's name as it's used in config.
+ * @type {String}
+ */
+ get name() {
+ return 'block-indent';
+ },
+
+ /**
+ * Name of option that must run after this option.
+ * @type {String}
+ */
+ get runBefore() {
+ return 'sort-order';
+ },
+
+ /**
+ * List of syntaxes that are supported by this option.
+ * @type {Array}
+ */
+ get syntax() {
+ return ['css', 'less', 'sass', 'scss'];
+ },
+
+ /**
+ * Types of values this option accepts in config.
+ * @type {Object}
+ */
+ get accepts() {
+ return {
+ number: true,
+ string: /^[ \t]*$/
+ };
+ },
+
+ /**
+ * Processes ast and fixes found code style errors.
+ * @param {Node} ast
+ */
+ process(ast) {
+ ast.eachFor('space', function(whitespaceNode, i) {
+ var spaces = whitespaceNode.content.replace(/\n[ \t]+/gm, '\n');
+
+ if (spaces === '') {
+ ast.removeChild(i);
+ } else {
+ whitespaceNode.content = spaces;
+ }
+ });
+
+ this._processNode(ast, 0);
+ },
+
+ /**
+ * Detects the value of this option in ast.
+ * @param {Node} ast
+ * @return {Array} List of detected values
+ */
+ detect(ast) {
+ var detected = [];
+
+ ast.traverse(function(node) {
+ // Continue only with non-empty {...} blocks:
+ if (!node.is('atrulers') && !node.is('block') || !node.length)
+ return;
+
+ node.eachFor('space', function(whitespaceNode) {
+ var spaces = whitespaceNode.content;
+ var lastIndex = spaces.lastIndexOf('\n');
+
+ // Do not continue if there is no line break:
+ if (lastIndex < 0) return;
+
+ // Number of spaces from beginning of line:
+ var spacesLength = spaces.slice(lastIndex + 1).length + 1;
+ detected.push(new Array(spacesLength).join(' '));
+ });
+ });
+
+ return detected;
+ },
+
+ /**
+ * @param {Node} node
+ * @param {Number} level
+ */
+ _processNode(node, level) {
+ var that = this;
+
+ node.forEach(function(n) {
+ if (node.syntax === 'sass' && n.is('block')) {
+ that._processSassBlock(n, level);
+ }
+
+ // Continue only with space nodes inside {...}:
+ if (node.syntax !== 'sass' && level !== 0 && n.is('space')) {
+ that._processSpaceNode(n, level);
+ }
+
+ if (n.is('block') || n.is('atrulers')) level++;
+
+ that._processNode(n, level);
+ });
+ },
+
+ /**
+ * @param {Node} node
+ * @param {Number} level
+ */
+ _processSassBlock(node, level) {
+ var value = this.value;
+
+ node.eachFor('space', function(whitespaceNode) {
+ if (whitespaceNode.content === '\n') return;
+
+ var spaces = whitespaceNode.content.replace(/[ \t]/gm, '');
+ spaces += new Array(level + 2).join(value);
+ whitespaceNode.content = spaces;
+ });
+ },
+
+ /**
+ * @param {Node} node
+ * @param {Number} level
+ */
+ _processSpaceNode(node, level) {
+ var value = this.value;
+
+ // Remove all whitespaces and tabs, leave only new lines:
+ var spaces = node.content.replace(/[ \t]/gm, '');
+
+ if (!spaces) return;
+
+ spaces += new Array(level + 1).join(value);
+ node.content = spaces;
+ }
+};
+
+module.exports = option;
diff --git a/src/options/color-case.js b/src/options/color-case.js
new file mode 100644
index 00000000..b29ca12b
--- /dev/null
+++ b/src/options/color-case.js
@@ -0,0 +1,64 @@
+'use strict';
+
+let option = {
+ /**
+ * Option's name as it's used in config.
+ * @type {String}
+ */
+ get name() {
+ return 'color-case';
+ },
+
+ /**
+ * List of syntaxes that are supported by this option.
+ * @type {Array}
+ */
+ get syntax() {
+ return ['css', 'less', 'sass', 'scss'];
+ },
+
+ /**
+ * Types of values this option accepts in config.
+ * @type {Object}
+ */
+ get accepts() {
+ return {
+ string: /^lower|upper$/
+ };
+ },
+
+ /**
+ * Processes ast and fixes found code style errors.
+ * @param {Node} ast
+ */
+ process(ast) {
+ var value = this.value;
+
+ ast.traverseByType('color', function(color) {
+ color.content = value === 'lower' ?
+ color.content.toLowerCase() :
+ color.content.toUpperCase();
+ });
+ },
+
+ /**
+ * Detects the value of this option in ast.
+ * @param {Node} ast
+ * @return {Array} List of detected values
+ */
+ detect(ast) {
+ var detected = [];
+
+ ast.traverseByType('color', function(color) {
+ if (color.content.match(/^[^A-F]*[a-f][^A-F]*$/)) {
+ detected.push('lower');
+ } else if (color.content.match(/^[^a-f]*[A-F][^a-f]*$/)) {
+ detected.push('upper');
+ }
+ });
+
+ return detected;
+ }
+};
+
+module.exports = option;
diff --git a/src/options/color-shorthand.js b/src/options/color-shorthand.js
new file mode 100644
index 00000000..5abbfdca
--- /dev/null
+++ b/src/options/color-shorthand.js
@@ -0,0 +1,64 @@
+'use strict';
+
+let option = {
+ /**
+ * Option's name as it's used in config.
+ * @type {String}
+ */
+ get name() {
+ return 'color-shorthand';
+ },
+
+ /**
+ * List of syntaxes that are supported by this option.
+ * @type {Array}
+ */
+ get syntax() {
+ return ['css', 'less', 'sass', 'scss'];
+ },
+
+ /**
+ * Types of values this option accepts in config.
+ * @type {Object}
+ */
+ get accepts() {
+ return {
+ boolean: [true, false]
+ };
+ },
+
+ /**
+ * Processes ast and fixes found code style errors.
+ * @param {Node} ast
+ */
+ process(ast) {
+ var value = this.value;
+
+ ast.traverseByType('color', function(color) {
+ color.content = value ?
+ color.content.replace(/(\w)\1(\w)\2(\w)\3/i, '$1$2$3') :
+ color.content.replace(/^(\w)(\w)(\w)$/, '$1$1$2$2$3$3');
+ });
+ },
+
+ /**
+ * Detects the value of this option in ast.
+ * @param {Node} ast
+ * @return {Array} List of detected values
+ */
+ detect(ast) {
+ var detected = [];
+
+ ast.traverseByType('color', function(color) {
+ if (color.content.match(/^\w{3}$/)) {
+ detected.push(true);
+ } else if (color.content.match(/^(\w)\1(\w)\2(\w)\3$/)) {
+ detected.push(false);
+ }
+ });
+
+ return detected;
+ }
+};
+
+module.exports = option;
diff --git a/src/options/element-case.js b/src/options/element-case.js
new file mode 100644
index 00000000..3bd7a50c
--- /dev/null
+++ b/src/options/element-case.js
@@ -0,0 +1,76 @@
+'use strict';
+
+let option = {
+ /**
+ * Option's name as it's used in config.
+ * @type {String}
+ */
+ get name() {
+ return 'element-case';
+ },
+
+ /**
+ * List of syntaxes that are supported by this option.
+ * @type {Array}
+ */
+ get syntax() {
+ return ['css', 'less', 'sass', 'scss'];
+ },
+
+ /**
+ * Types of values this option accepts in config.
+ * @type {Object}
+ */
+ get accepts() {
+ return {
+ string: /^lower|upper$/
+ };
+ },
+
+ /**
+ * Processes ast and fixes found code style errors.
+ * @param {Node} ast
+ */
+ process(ast) {
+ let value = this.value;
+
+ ast.traverse((node) => {
+ if (!node.is('selector') && !node.is('arguments')) return;
+
+ node.forEach('typeSelector', (selector) => {
+ selector.forEach('ident', (ident) => {
+ ident.content = value === 'lower' ?
+ ident.content.toLowerCase() :
+ ident.content.toUpperCase();
+ });
+ });
+ });
+ },
+
+ /**
+ * Detects the value of this option in ast.
+ * @param {Node} ast
+ * @return {Array} List of detected values
+ */
+ detect(ast) {
+ let detected = [];
+
+ ast.traverse((node) => {
+ if (!node.is('selector') && !node.is('arguments')) return;
+
+ node.forEach('typeSelector', (selector) => {
+ selector.forEach('ident', (ident) => {
+ if (ident.content.match(/^[a-z]+$/)) {
+ detected.push('lower');
+ } else if (ident.content.match(/^[A-Z]+$/)) {
+ detected.push('upper');
+ }
+ });
+ });
+ });
+
+ return detected;
+ }
+};
+
+module.exports = option;
diff --git a/src/options/eof-newline.js b/src/options/eof-newline.js
new file mode 100644
index 00000000..29196cd4
--- /dev/null
+++ b/src/options/eof-newline.js
@@ -0,0 +1,64 @@
+'use strict';
+
+let gonzales = require('gonzales-pe');
+
+let option = {
+ /**
+ * Option's name as it's used in config.
+ * @type {String}
+ */
+ get name() {
+ return 'eof-newline';
+ },
+
+ /**
+ * List of syntaxes that are supported by this option.
+ * @type {Array}
+ */
+ get syntax() {
+ return ['css', 'less', 'sass', 'scss'];
+ },
+
+ /**
+ * Types of values this option accepts in config.
+ * @type {Object}
+ */
+ get accepts() {
+ return {
+ boolean: [true, false]
+ };
+ },
+
+ /**
+ * Processes ast and fixes found code style errors.
+ * @param {Node} ast
+ */
+ process(ast) {
+ var lastChild = ast.last();
+
+ if (!lastChild.is('space')) {
+ lastChild = gonzales.createNode({type: 'space', content: ''});
+ ast.content.push(lastChild);
+ }
+
+ lastChild.content = lastChild.content.replace(/\n$/, '');
+ if (this.value) lastChild.content += '\n';
+ },
+
+ /**
+ * Detects the value of this option in ast.
+ * @param {Node} ast
+ * @return {Array} List of detected values
+ */
+ detect(ast) {
+ var lastChild = ast.last();
+
+ if (lastChild.is('space') && lastChild.content.indexOf('\n') !== -1) {
+ return [true];
+ } else {
+ return [false];
+ }
+ }
+};
+
+module.exports = option;
diff --git a/src/options/leading-zero.js b/src/options/leading-zero.js
new file mode 100644
index 00000000..77381c39
--- /dev/null
+++ b/src/options/leading-zero.js
@@ -0,0 +1,46 @@
+'use strict';
+
+module.exports = {
+ name: 'leading-zero',
+
+ syntax: ['css', 'less', 'sass', 'scss'],
+
+ accepts: {
+ boolean: [true, false]
+ },
+
+ /**
+ * Processes tree node.
+ * @param {node} ast
+ */
+ process: function(ast) {
+ let value = this.value;
+
+ ast.traverseByType('number', function(number) {
+ if (!value) {
+ number.content = number.content.replace(/^0+(?=\.)/, '');
+ } else if (number.content[0] === '.') {
+ number.content = '0' + number.content;
+ }
+ });
+ },
+
+ /**
+ * Detects the value of an option at the tree node.
+ *
+ * @param {node} ast
+ */
+ detect: function(ast) {
+ let detected = [];
+
+ ast.traverseByType('number', function(number) {
+ if (number.content.match(/^\.[0-9]+/)) {
+ detected.push(false);
+ } else if (number.content.match(/^0\.[0-9]+/)) {
+ detected.push(true);
+ }
+ });
+
+ return detected;
+ }
+};
diff --git a/src/options/lines-between-rulesets.js b/src/options/lines-between-rulesets.js
new file mode 100644
index 00000000..15d99203
--- /dev/null
+++ b/src/options/lines-between-rulesets.js
@@ -0,0 +1,250 @@
+'use strict';
+
+let gonzales = require('gonzales-pe');
+
+let option = {
+ newLinesString: '',
+ newLinesNode: null,
+
+ /**
+ * Option's name as it's used in config.
+ * @type {String}
+ */
+ get name() {
+ return 'lines-between-rulesets';
+ },
+
+ /**
+ * Name of option that must run after this option.
+ * @type {String}
+ */
+ get runBefore() {
+ return 'block-indent';
+ },
+
+ /**
+ * List of syntaxes that are supported by this option.
+ * @type {Array}
+ */
+ get syntax() {
+ return ['css', 'less', 'sass', 'scss'];
+ },
+
+ /**
+ * Types of values this option accepts in config.
+ * @type {Object}
+ */
+ get accepts() {
+ return {
+ number: true
+ };
+ },
+
+ /**
+ * @param {number} value
+ * @returns {number}
+ */
+ /*
+ ** Still need to override, as the core implementation of setValue doesn't
+ ** pass numbers through, but creates a string of spaces of the same length.
+ */
+ setValue(value) {
+ let valueType = typeof value;
+
+ if (valueType !== 'number') {
+ throw new Error('Value must be a number.');
+ }
+
+ return value;
+ },
+
+ buildSpacing(syntax) {
+ let spacing = '';
+ let numNewLines = 0;
+ let newLinesOffset = 1;
+
+ if (syntax === 'sass') {
+ newLinesOffset = 0;
+ }
+
+ numNewLines = Math.round(this.value) + newLinesOffset;
+
+ for (var i = 0; i < numNewLines; i++) {
+ spacing += '\n';
+ }
+
+ return spacing;
+ },
+
+ /**
+ * Processes ast and fixes found code style errors.
+ * @param {Node} ast
+ */
+ process(ast) {
+ this.newLinesString = this.buildSpacing(ast.syntax);
+ this.newLinesNode = gonzales.createNode({
+ type: 'space',
+ content: this.newLinesString
+ });
+ this.processBlock(ast);
+ },
+
+ processBlock(x) {
+ if (x.is('stylesheet')) {
+ // Check all @rules
+ this.processAtRules(x);
+
+ // Check all rulesets
+ this.processRuleSets(x);
+ }
+
+ x.forEach((node) => {
+ if (!node.is('block')) {
+ return this.processBlock(node);
+ }
+
+ // Check all @rules
+ this.processAtRules(node);
+
+ // Check all rulesets
+ this.processRuleSets(node);
+
+ this.processBlock(node);
+ });
+ },
+
+ processAtRules(node) {
+ node.forEach('atrule', (atRuleNode, index) => {
+ this.insertNewlines(node, index);
+ });
+ },
+
+ processRuleSets(node) {
+ node.forEach('ruleset', (ruleSetNode, index) => {
+ this.insertNewlines(node, index);
+ });
+ },
+
+ isComment(node) {
+ if (!node) {
+ return false;
+ }
+ return (node.is('singlelineComment') || node.is('multilineComment'));
+ },
+
+ isNewline(node) {
+ if (!node) {
+ return false;
+ }
+ return (node.content === '\n');
+ },
+
+ prevLineIsComment(parent, index) {
+ let indexThreshold = 2;
+ let prevChild;
+ let prevMinusOneChild;
+ let prevMinusTwoChild;
+ let parentSyntax = parent ? parent.syntax : null;
+
+ // Sass is troublesome because newlines are counted as separate nodes
+ if (parentSyntax === 'sass') {
+ indexThreshold = 3;
+ }
+
+ if (!parent || index < indexThreshold) {
+ return false;
+ }
+
+ prevChild = parent.get(index - 1);
+ prevMinusOneChild = parent.get(index - 2);
+
+ if (parentSyntax === 'sass') {
+ prevMinusTwoChild = parent.get(index - 3);
+ return (
+ this.isComment(prevMinusTwoChild) &&
+ this.isNewline(prevMinusOneChild) &&
+ prevChild.is('space')
+ );
+ }
+
+ return (this.isComment(prevMinusOneChild) && prevChild.is('space'));
+ },
+
+ /*
+ ** Find the latest previous child that isn't a comment, and return its index.
+ */
+ findLatestNonCommentNode(parent, index) {
+ let prevChild;
+ let lastNonCommentIndex = -1;
+ let currentIndex = index;
+ let jumpSize = 2;
+
+ if (parent.syntax === 'sass') {
+ jumpSize = 3;
+ }
+
+ while (currentIndex >= 0) {
+ if (this.prevLineIsComment(parent, currentIndex)) {
+ currentIndex -= jumpSize;
+ continue;
+ }
+
+ prevChild = parent.get(currentIndex - 1);
+
+ if (!this.isComment(prevChild)) {
+ lastNonCommentIndex = currentIndex - 1;
+ break;
+ }
+
+ currentIndex--;
+ }
+
+ return lastNonCommentIndex;
+ },
+
+ insertNewlinesAsString(node) {
+ let content = node.content;
+ let lastNewline = content.lastIndexOf('\n');
+ let newContent;
+
+ if (lastNewline > -1) {
+ content = content.substring(lastNewline + 1);
+ }
+
+ newContent = this.newLinesString + content;
+ node.content = newContent;
+ },
+
+ insertNewlinesAsNode(node) {
+ node.insert(node.length, this.newLinesNode);
+ },
+
+ insertNewlines(node, index) {
+ let prevChild = node.get(index - 1);
+ let shouldInsert = false;
+
+ // Check for previous nodes that are not a space
+ // Do not insert if the ruleset is the first item
+ for (var i = 0; i < index; i++) {
+ if (!node.get(i).is('space')) {
+ shouldInsert = true;
+ break;
+ }
+ }
+
+ if (prevChild && shouldInsert) {
+ if (this.prevLineIsComment(node, index) || this.isComment(prevChild)) {
+ let lastNonCommentIndex = this.findLatestNonCommentNode(node, index);
+ prevChild = node.get(lastNonCommentIndex);
+ }
+
+ if (prevChild.is('space')) {
+ this.insertNewlinesAsString(prevChild);
+ } else {
+ this.insertNewlinesAsNode(prevChild);
+ }
+ }
+ }
+};
+
+module.exports = option;
diff --git a/src/options/quotes.js b/src/options/quotes.js
new file mode 100644
index 00000000..3e591783
--- /dev/null
+++ b/src/options/quotes.js
@@ -0,0 +1,58 @@
+'use strict';
+
+module.exports = {
+ name: 'quotes',
+
+ syntax: ['css', 'less', 'sass', 'scss'],
+
+ accepts: {
+ string: /^single|double$/
+ },
+
+ /**
+ * Processes tree node.
+ * @param {node} ast
+ */
+ process: function(ast) {
+ let value = this.value;
+
+ ast.traverseByType('string', function(string) {
+ if (string.content[0] === '"' && value === 'single') {
+ string.content = string.content
+ // Unescape all escaped double quotes
+ .replace(/\\"/g, '"')
+ // Escape all the single quotes
+ .replace(/([^\\])'/g, '$1\\\'')
+ // Replace the first and the last quote
+ .replace(/^"|"$/g, '\'');
+ } else if (string.content[0] === '\'' && value === 'double') {
+ string.content = string.content
+ // Unescape all escaped single quotes
+ .replace(/\\'/g, '\'')
+ // Escape all the double quotes
+ .replace(/([^\\])"/g, '$1\\\"')
+ // Replace the first and the last quote
+ .replace(/^'|'$/g, '"');
+ }
+ });
+ },
+
+ /**
+ * Detects the value of an option at the tree node.
+ *
+ * @param {node} ast
+ */
+ detect: function(ast) {
+ let detected = [];
+
+ ast.traverseByType('string', function(string) {
+ if (string.content[0] === '"') {
+ detected.push('double');
+ } else if (string.content[0] === '\'') {
+ detected.push('single');
+ }
+ });
+
+ return detected;
+ }
+};
diff --git a/src/options/remove-empty-rulesets.js b/src/options/remove-empty-rulesets.js
new file mode 100644
index 00000000..faafd51f
--- /dev/null
+++ b/src/options/remove-empty-rulesets.js
@@ -0,0 +1,91 @@
+'use strict';
+
+module.exports = (function() {
+ function processNode(node) {
+ removeEmptyRulesets(node);
+ mergeAdjacentWhitespace(node);
+ }
+
+ function removeEmptyRulesets(stylesheet) {
+ stylesheet.forEach('ruleset', function(ruleset, i) {
+ var block = ruleset.first('block');
+ processNode(block);
+ if (isEmptyBlock(block)) stylesheet.removeChild(i);
+ });
+ }
+
+ /**
+ * Removing ruleset nodes from tree may result in two adjacent whitespace
+ * nodes which is not correct AST:
+ * [space, ruleset, space] => [space, space]
+ * To ensure correctness of further processing we should merge such nodes
+ * into one:
+ * [space, space] => [space]
+ */
+ function mergeAdjacentWhitespace(node) {
+ var i = node.content.length - 1;
+ while (i-- > 0) {
+ if (node.get(i).is('space') &&
+ node.get(i + 1) && node.get(i + 1).is('space')) {
+ node.get(i).content += node.get(i + 1).content;
+ node.removeChild(i + 1);
+ }
+ }
+ }
+
+ /**
+ * Block is considered empty when it has nothing but spaces.
+ */
+ function isEmptyBlock(node) {
+ if (!node.length) return true;
+
+ return !node.content.some(function(node) {
+ return !node.is('space');
+ });
+ }
+
+ return {
+ name: 'remove-empty-rulesets',
+
+ runBefore: 'block-indent',
+
+ syntax: ['css', 'less', 'sass', 'scss'],
+
+ accepts: {
+ boolean: [true]
+ },
+
+ /**
+ * Remove rulesets with no declarations.
+ *
+ * @param {String} ast
+ */
+ process: function(ast) {
+ processNode(ast);
+ },
+
+ detectDefault: true,
+
+ /**
+ * Detects the value of an option at the tree node.
+ * This option is treated as `true` by default, but any trailing space
+ * would invalidate it.
+ *
+ * @param {node} ast
+ */
+ detect: function(ast) {
+ let detected = [];
+
+ ast.traverse(function(node) {
+ if (!node.is('atrulers') && !node.is('block')) return;
+
+ if (node.length === 0 ||
+ (node.length === 1 && node.first().is('space'))) {
+ detected.push(false);
+ }
+ });
+
+ return detected;
+ }
+ };
+})();
diff --git a/src/options/sort-order-fallback.js b/src/options/sort-order-fallback.js
new file mode 100644
index 00000000..318094db
--- /dev/null
+++ b/src/options/sort-order-fallback.js
@@ -0,0 +1,13 @@
+'use strict';
+
+module.exports = {
+ name: 'sort-order-fallback',
+
+ syntax: ['css', 'less', 'sass', 'scss'],
+
+ accepts: {string: /^abc$/},
+
+ process: function() {},
+
+ detect: () => []
+};
diff --git a/src/options/sort-order.js b/src/options/sort-order.js
new file mode 100644
index 00000000..27a2198a
--- /dev/null
+++ b/src/options/sort-order.js
@@ -0,0 +1,432 @@
+'use strict';
+
+var gonzales = require('gonzales-pe');
+
+module.exports = {
+ get name() {
+ return 'sort-order';
+ },
+
+ get runBefore() {
+ return 'space-before-closing-brace';
+ },
+
+ get syntax() {
+ return ['css', 'less', 'sass', 'scss'];
+ },
+
+ /**
+ * @param {Array} value Option value
+ * @returns {Array}
+ */
+ setValue(value) {
+ if (!Array.isArray(value))
+ throw new Error('The option accepts only array of properties.');
+
+ var order = {};
+
+ if (typeof value[0] === 'string') {
+ // If there is only one group of properties.
+ value.forEach(function(prop, propIndex) {
+ order[prop] = {group: 0, prop: propIndex};
+ });
+ } else {
+ value.forEach(function(group, groupIndex) {
+ group.forEach(function(prop, propIndex) {
+ order[prop] = {group: groupIndex, prop: propIndex};
+ });
+ });
+ }
+
+ return order;
+ },
+
+ /**
+ * @param {node} ast
+ * @param {object} config
+ */
+ process(ast, config) {
+ this._config = config;
+ // Sort properties only inside blocks.
+ ast.traverseByType('block', this._processBlock.bind(this));
+ },
+
+ _cleanSassLinebreaks(node) {
+ let containsOnlyLinebreaks = true;
+
+ node.forEach((space) => {
+ if (!space.is('space') || space.content !== '\n') {
+ containsOnlyLinebreaks = false;
+ return null;
+ }
+ });
+
+ if (containsOnlyLinebreaks)
+ node.content = [];
+ },
+
+ _extendNode(block, i, spacesBefore) {
+ let nodesToDelete = [i];
+ let node = block.get(i);
+ let extendedNode = {i: i, node: node};
+
+ let propertyName = this._getSortableName(node);
+ if (!propertyName) return null;
+
+ // Check if current node's property name is in sort order.
+ let propertyIndex = this.value[propertyName];
+ // If the declaration's property is in order's list, save its
+ // group and property indices. Otherwise set them to 10000, so
+ // declaration appears at the bottom of a sorted list:
+ extendedNode.groupIndex = propertyIndex && propertyIndex.group > -1 ?
+ propertyIndex.group : this._getLastGroupIndex();
+ extendedNode.propertyIndex = propertyIndex && propertyIndex.prop > -1 ?
+ propertyIndex.prop : this._getLastPropertyIndex();
+
+ // Spaces before node.
+ nodesToDelete = nodesToDelete.concat(spacesBefore);
+ extendedNode.spacesBeforeNode =
+ this._getNodesByIndex(block, spacesBefore);
+
+ // Spaces after node.
+ let spacesBeforeDelimiter =
+ this._getSpacesAndCommentsAfterNode(block, i);
+ nodesToDelete = nodesToDelete.concat(spacesBeforeDelimiter);
+ extendedNode.spacesBeforeDelimiter =
+ this._getNodesByIndex(block, spacesBeforeDelimiter);
+
+ i += spacesBeforeDelimiter.length;
+
+ // Spaces after delimiter.
+ // If there is `;` right after the declaration, save it with the
+ // declaration and mark it for removing from parent node:
+ if (block.get(i + 1) && block.get(i + 1).is('declarationDelimiter')) {
+ i += 1;
+ node = block.get(i);
+ nodesToDelete.push(i);
+ extendedNode.delim = node;
+
+ if (node.syntax !== 'sass') {
+ // Save spaces and comments which follow right after
+ // the declaration and mark them for removing from parent node:
+ let spacesAfterDelimiter =
+ this._getSpacesAndCommentsAfterNode(block, i);
+ i += spacesAfterDelimiter.length;
+ nodesToDelete = nodesToDelete.concat(spacesAfterDelimiter);
+ extendedNode.spacesAfterDelimiter =
+ this._getNodesByIndex(block, spacesAfterDelimiter);
+ }
+ }
+
+ extendedNode.endIndex = i;
+ // Remove all nodes, that were moved to `sortables` list,
+ // from block node:
+ extendedNode.nodesToDelete = nodesToDelete;
+
+ return extendedNode;
+ },
+
+ _getLastGroupIndex() {
+ return this.value && this.value['...'] ?
+ this.value['...'].group : Infinity;
+ },
+
+ _getLastPropertyIndex() {
+ return this.value && this.value['...'] ?
+ this.value['...'].prop : Infinity;
+ },
+
+ _getNodesByIndex(block, index) {
+ return index.map((i) => block.get(i));
+ },
+
+ _getSortableIncludeName(node) {
+ // Divide `include` into mixins with specific name
+ // (e. g. `$include breakpoint`), and the rest — `$include`.
+ let mixinName;
+
+ if (node.syntax === 'less') {
+ // `node.first()` is class and `node.first().first()` is ident.
+ mixinName = node.first().first().content;
+ } else if (node.syntax === 'sass' && node.first().content === '+') {
+ // `node.first()` is `+` and `node.get(1)` is ident.
+ mixinName = node.get(1).content;
+ } else {
+ // `node.first()` is @-keyword, `node.get(1)` is space and
+ // `node.get(2)` is ident.
+ mixinName = node.get(2).content;
+ }
+
+ let includeMixinName = '$include ' + mixinName;
+ return this.value.hasOwnProperty(includeMixinName) ?
+ includeMixinName :
+ '$include';
+ },
+
+ _getSortableName(node) {
+ if (node.is('extend')) return '$extend';
+ if (node.is('include')) return this._getSortableIncludeName(node);
+ else return this._getSortablePropertyName(node);
+ },
+
+ _getSortablePropertyName(node) {
+ if (node.is('declaration')) {
+ let property = node.first('property').first();
+ return property.is('variable') ? '$variable' : property.content;
+ }
+
+ let atkeyword = node.first('atkeyword');
+ if (atkeyword && atkeyword.first().content === 'import')
+ return '$import';
+ },
+
+ _getSpacesAndCommentsAfterNode(node, i) {
+ // List of start positions for nodes with spaces and comments:
+ let positions = [];
+
+ // Skip node itself.
+ i++;
+
+ for (let l = node.length; i < l; i++) {
+ let currentNode = node.get(i);
+
+ // If node is nor spaces neither comment, stop.
+ if (!this._isSpaceOrComment(currentNode)) break;
+
+ if (currentNode.is('multilineComment') ||
+ currentNode.is('singlelineComment')) {
+ positions.push(i);
+ continue;
+ }
+
+ // If there are any line breaks in a node with spaces, stop and
+ // split the node into two: one with spaces before line break
+ // and one with `\n` symbol and everything that goes after.
+ // Combine the first one with declaration/@-rule's node:
+ let linebreakIndex = currentNode.content.indexOf('\n');
+ if (linebreakIndex !== -1) {
+ var s = currentNode.content.substring(0, linebreakIndex);
+ if (s === '') break;
+ var space = gonzales.createNode({type: 'space', content: s});
+ node.insert(i + 1, space);
+ positions.push(i + 1);
+ currentNode.content = currentNode.content
+ .substring(linebreakIndex);
+ break;
+ }
+
+ positions.push(i);
+ }
+
+ return positions;
+ },
+
+ /**
+ * Check if there are any comments or spaces before
+ * the declaration/@-rule.
+ * @param {Node} node
+ * @param {Number} i
+ * @returns {Array} List of nodes with spaces and comments
+ */
+ _getSpacesAndCommentsBeforeNode(node, i) {
+ // List of start positions for nodes with spaces and comments:
+ let positions = [];
+ let sendPositions = false;
+
+ for (let l = node.length; i < l; i++) {
+ let currentNode = node.get(i);
+
+ // If the node is declaration or @-rule, stop and return all
+ // found nodes with spaces and comments (if there are any):
+ if (!this._isSpaceOrComment(currentNode)) {
+ sendPositions = true;
+ break;
+ }
+
+ positions.push(i);
+ }
+
+ return sendPositions ? positions : null;
+ },
+
+ _insertSortablesToBlock(nodesToSort, node) {
+ if (node.syntax === 'sass')
+ this._cleanSassLinebreaks(node);
+
+ for (let i = nodesToSort.length - 1, l = -1; i > l; i--) {
+ let currentNode = nodesToSort[i];
+ let prevNode = nodesToSort[i - 1];
+ let spacesBeforeNode = currentNode.spacesBeforeNode || [];
+ let spacesBeforeDelimiter = currentNode.spacesBeforeDelimiter || [];
+ let spacesAfterDelimiter = currentNode.spacesAfterDelimiter || [];
+
+ if (node.syntax === 'sass' && spacesBeforeNode.length) {
+ let space = spacesBeforeNode[0];
+ space.content = space.content.replace(/\n/, '');
+ }
+
+ spacesBeforeNode.reverse().map(this._removeEmptyLines);
+ spacesBeforeDelimiter.reverse().map(this._removeEmptyLines);
+ spacesAfterDelimiter.reverse().map(this._removeEmptyLines);
+
+ // Divide declarations from different groups with
+ // an empty line:
+ if (prevNode && currentNode.groupIndex > prevNode.groupIndex) {
+ let space = spacesBeforeNode[0];
+ if (space && space.is('space') &&
+ (space.syntax === 'sass' ||
+ space.content.match(/\n/g) &&
+ space.content.match(/\n/g).length < 2)) {
+ space.content = '\n' + space.content;
+ }
+ }
+
+ for (let j = 0, nl = spacesAfterDelimiter.length; j < nl; j++) {
+ node.content.unshift(spacesAfterDelimiter[j]);
+ }
+
+ if (currentNode.delim) {
+ node.content.unshift(currentNode.delim);
+ } else if (i !== nodesToSort.length - 1 &&
+ (currentNode.node.is('declaration') ||
+ currentNode.node.is('extend'))) {
+ let delimiter = gonzales.createNode({
+ type: 'declarationDelimiter',
+ content: currentNode.node.syntax === 'sass' ? '\n' : ';'
+ });
+ node.content.unshift(delimiter);
+ }
+
+ for (let j = 0, nl = spacesBeforeDelimiter.length; j < nl; j++) {
+ node.content.unshift(spacesBeforeDelimiter[j]);
+ }
+
+ node.content.unshift(currentNode.node);
+
+ for (let j = 0, nl = spacesBeforeNode.length; j < nl; j++) {
+ node.content.unshift(spacesBeforeNode[j]);
+ }
+ }
+ },
+
+ // Types of nodes that can be sorted.
+ _isAcceptableNode(node) {
+ const NODES = ['atrule', 'declaration', 'extend', 'include',
+ 'multilineComment', 'singlelineComment', 'space'];
+ return NODES.indexOf(node.type) !== -1;
+ },
+
+ // Spaces and comments.
+ _isSpaceOrComment(node) {
+ const SC = ['multilineComment', 'singlelineComment', 'space'];
+ return SC.indexOf(node.type) !== -1;
+ },
+
+ _processBlock(block) {
+ // Check every child node.
+ // If it is declaration (property-value pair, e.g. `color: tomato`),
+ // or @-rule (e.g. `@include nani`),
+ // combine it with spaces, semicolon and comments and move them from
+ // current node to a separate list for further sorting:
+ let nodesToSort = this._separateSortablesFromBlock(block);
+ this._sortNodes(nodesToSort);
+ this._insertSortablesToBlock(nodesToSort, block);
+ },
+
+ /**
+ * Remove empty lines in space node.
+ * @param {node} node Space node.
+ */
+ _removeEmptyLines(node) {
+ node.content = node.content.replace(/\n[\s\t\n\r]*\n/, '\n');
+ },
+
+ _separateSortablesFromBlock(block) {
+ let sortables = [];
+ let nodesToDelete = [];
+
+ // Don't cache `block.length` since we may insert new nodes into it.
+ for (let i = 0; i < block.length; i++) {
+ let node = block.get(i);
+ if (!this._isAcceptableNode(node)) continue;
+
+ // Save preceding spaces and comments, if there are any,
+ // and mark them for removing from parent node:
+ let spacesBeforeNode =
+ this._getSpacesAndCommentsBeforeNode(block, i);
+ if (!spacesBeforeNode) break;
+
+ i += spacesBeforeNode.length;
+ node = block.get(i);
+
+ let extendedNode = this._extendNode(block, i, spacesBeforeNode);
+ if (!extendedNode) continue;
+
+ nodesToDelete = nodesToDelete.concat(extendedNode.nodesToDelete);
+ i = extendedNode.endIndex;
+ sortables.push(extendedNode);
+ }
+
+ nodesToDelete.sort((a, b) => a - b);
+ for (let x = nodesToDelete.length - 1; x > -1; x--)
+ block.removeChild(nodesToDelete[x]);
+
+ return sortables;
+ },
+
+ _sortLeftovers(a, b) {
+ let prefixes = ['-webkit-', '-moz-', '-ms-', '-o-', ''];
+ let prefixesRegExp = /^(-webkit-|-moz-|-ms-|-o-)(.*)$/;
+
+ // Get property name (i.e. `color`, `-o-animation`):
+ a = a.node.first().first().content;
+ b = b.node.first().first().content;
+
+ // Get prefix and unprefixed part. For example:
+ // ['-o-animation', '-o-', 'animation']
+ // ['color', '', 'color']
+ a = a.match(prefixesRegExp) || [a, '', a];
+ b = b.match(prefixesRegExp) || [b, '', b];
+
+ if (a[2] !== b[2]) {
+ // If unprefixed parts are different (i.e. `border` and
+ // `color`), compare them:
+ return a[2] <= b[2] ? -1 : 1;
+ } else {
+ // If unprefixed parts are identical (i.e. `border` in
+ // `-moz-border` and `-o-border`), compare prefixes.
+ // They should go in the same order they are set
+ // in `prefixes` array.
+ return prefixes.indexOf(a[1]) <= prefixes.indexOf(b[1]) ? -1 : 1;
+ }
+ },
+
+ _sortNodes(nodes) {
+ nodes.sort((a, b) => {
+ // If a's group index is higher than b's group index, in
+ // a sorted list a appears after b:
+ if (a.groupIndex !== b.groupIndex)
+ return a.groupIndex - b.groupIndex;
+
+ // If a and b belong to leftovers and `sort-order-fallback`
+ // option is set to `abc`, sort properties alphabetically:
+ if (a.groupIndex === this._getLastGroupIndex() &&
+ this._config['sort-order-fallback']) {
+ return this._sortLeftovers(a, b);
+ }
+
+ // If a and b have the same group index, and a's property index
+ // is higher than b's property index, in a sorted list
+ // a appears after b:
+ if (a.propertyIndex !== b.propertyIndex)
+ return a.propertyIndex - b.propertyIndex;
+
+ // If a and b have the same group index and the same property
+ // index, in a sorted list they appear in the same order
+ // they were in original array:
+ return a.i - b.i;
+ });
+ },
+
+ detect: () => []
+};
diff --git a/src/options/space-after-colon.js b/src/options/space-after-colon.js
new file mode 100644
index 00000000..bd3007fb
--- /dev/null
+++ b/src/options/space-after-colon.js
@@ -0,0 +1,65 @@
+'use strict';
+
+var gonzales = require('gonzales-pe');
+
+module.exports = {
+ name: 'space-after-colon',
+
+ runBefore: 'block-indent',
+
+ syntax: ['css', 'less', 'sass', 'scss'],
+
+ accepts: {
+ number: true,
+ string: /^[ \t\n]*$/
+ },
+
+ /**
+ * Processes tree node.
+ *
+ * @param {node} ast
+ */
+ process: function(ast) {
+ let value = this.value;
+
+ ast.traverseByType('propertyDelimiter', function(delimiter, i, parent) {
+ if (delimiter.syntax === 'sass' && !parent.get(i - 1))
+ return null;
+
+ // Remove any spaces after colon:
+ if (parent.get(i + 1) && parent.get(i + 1).is('space'))
+ parent.removeChild(i + 1);
+ // If the value set in config is not empty, add spaces:
+ if (value !== '') {
+ var space = gonzales.createNode({
+ type: 'space',
+ content: value
+ });
+ parent.insert(i + 1, space);
+ }
+
+ return null;
+ });
+ },
+
+ /**
+ * Detects the value of an option at the tree node.
+ *
+ * @param {node} ast
+ */
+ detect: function(ast) {
+ let detected = [];
+
+ ast.traverseByType('propertyDelimiter', function(delimiter, i, parent) {
+ var nextNode = parent.get(i + 1);
+
+ if (nextNode.is('space')) {
+ detected.push(nextNode.content);
+ } else {
+ detected.push('');
+ }
+ });
+
+ return detected;
+ }
+};
diff --git a/src/options/space-after-combinator.js b/src/options/space-after-combinator.js
new file mode 100644
index 00000000..556204b4
--- /dev/null
+++ b/src/options/space-after-combinator.js
@@ -0,0 +1,60 @@
+'use strict';
+
+var gonzales = require('gonzales-pe');
+
+module.exports = {
+ name: 'space-after-combinator',
+
+ runBefore: 'block-indent',
+
+ syntax: ['css', 'less', 'sass', 'scss'],
+
+ accepts: {
+ number: true,
+ string: /^[ \t\n]*$/
+ },
+
+ /**
+ * Processes tree node.
+ *
+ * @param {node} ast
+ */
+ process: function(ast) {
+ let value = this.value;
+
+ ast.traverseByType('combinator', function(combinator, i, parent) {
+ var nextNode = parent.get(i + 1);
+
+ if (nextNode && nextNode.is('space')) {
+ nextNode.content = value;
+ } else {
+ var space = gonzales.createNode({
+ type: 'space',
+ content: value
+ });
+ parent.insert(i + 1, space);
+ }
+ });
+ },
+
+ /**
+ * Detects the value of an option at the tree node.
+ *
+ * @param {node} ast
+ */
+ detect: function(ast) {
+ let detected = [];
+
+ ast.traverseByType('combinator', function(combinator, i, parent) {
+ var nextNode = parent.get(i + 1);
+
+ if (nextNode.is('space')) {
+ detected.push(nextNode.content);
+ } else {
+ detected.push('');
+ }
+ });
+
+ return detected;
+ }
+};
diff --git a/src/options/space-after-opening-brace.js b/src/options/space-after-opening-brace.js
new file mode 100644
index 00000000..ed04b1f3
--- /dev/null
+++ b/src/options/space-after-opening-brace.js
@@ -0,0 +1,62 @@
+'use strict';
+
+var gonzales = require('gonzales-pe');
+
+module.exports = {
+ name: 'space-after-opening-brace',
+
+ runBefore: 'block-indent',
+
+ syntax: ['css', 'less', 'scss'],
+
+ accepts: {
+ number: true,
+ string: /^[ \t\n]*$/
+ },
+
+ /**
+ * Processes tree node.
+ *
+ * @param {node} ast
+ */
+ process: function(ast) {
+ let value = this.value;
+
+ ast.traverse(function(node) {
+ // If found block node stop at the next one for space check
+ if (!node.is('block') && !node.is('atrulers')) return;
+
+ if (node.first() &&
+ node.first().is('space')) {
+ node.first().content = value;
+ } else if (value !== '') {
+ var space = gonzales.createNode({
+ type: 'space',
+ content: value
+ });
+ node.insert(0, space);
+ }
+ });
+ },
+
+ /**
+ * Detects the value of an option at the tree node.
+ *
+ * @param {node} ast
+ */
+ detect: function(ast) {
+ let detected = [];
+
+ ast.traverse(function(node) {
+ if (!node.is('block') && !node.is('atrulers')) return;
+
+ if (node.first().is('space')) {
+ detected.push(node.first().content);
+ } else {
+ detected.push('');
+ }
+ });
+
+ return detected;
+ }
+};
diff --git a/src/options/space-after-selector-delimiter.js b/src/options/space-after-selector-delimiter.js
new file mode 100644
index 00000000..995ed373
--- /dev/null
+++ b/src/options/space-after-selector-delimiter.js
@@ -0,0 +1,69 @@
+'use strict';
+
+var gonzales = require('gonzales-pe');
+
+module.exports = {
+ name: 'space-after-selector-delimiter',
+
+ runBefore: 'block-indent',
+
+ syntax: ['css', 'less', 'sass', 'scss'],
+
+ accepts: {
+ number: true,
+ string: /^[ \t\n]*$/
+ },
+
+ /**
+ * Processes tree node.
+ *
+ * @param {node} ast
+ */
+ process: function(ast) {
+ let value = this.value;
+
+ ast.traverseByType('delimiter', function(delimiter, i, parent) {
+ if (parent.is('arguments')) return;
+
+ var nextNode = parent.get(i + 1);
+ if (!nextNode) return;
+
+ if (nextNode.is('space')) {
+ nextNode.content = value;
+ } else if (nextNode.first().is('space')) {
+ nextNode.first().content = value;
+ } else {
+ var space = gonzales.createNode({
+ type: 'space',
+ content: value
+ });
+ nextNode.insert(0, space);
+ }
+ });
+ },
+
+ /**
+ * Detects the value of an option at the tree node.
+ *
+ * @param {node} ast
+ */
+ detect: function(ast) {
+ let detected = [];
+
+ ast.traverseByType('delimiter', function(delimiter, i, parent) {
+ if (parent.is('arguments')) return;
+
+ var nextNode = parent.get(i + 1);
+
+ if (nextNode && nextNode.is('space')) {
+ detected.push(nextNode.content);
+ } else if (nextNode.first() && nextNode.first().is('space')) {
+ detected.push(nextNode.first().content);
+ } else {
+ detected.push('');
+ }
+ });
+
+ return detected;
+ }
+};
diff --git a/src/options/space-before-closing-brace.js b/src/options/space-before-closing-brace.js
new file mode 100644
index 00000000..1cd276e2
--- /dev/null
+++ b/src/options/space-before-closing-brace.js
@@ -0,0 +1,104 @@
+'use strict';
+
+var gonzales = require('gonzales-pe');
+
+module.exports = (function() {
+ var valueFromSettings;
+ var blockIndent;
+
+ function getLastWhitespaceNode(node) {
+ var lastNode = node.last();
+
+ if (!lastNode || !lastNode.content) return null;
+
+ if (lastNode.is('block')) return null;
+ if (lastNode.is('space')) return lastNode;
+
+ return getLastWhitespaceNode(lastNode);
+ }
+
+ function processBlock(x, level) {
+ level = level || 0;
+
+ x.forEach(function(node) {
+ if (!node.is('block') &&
+ !node.is('atrulers')) return processBlock(node, level);
+
+ level++;
+
+ var value = valueFromSettings;
+ if (value.indexOf('\n') > -1) {
+ // TODO: Check that it works for '' block indent value
+ if (blockIndent) {
+ value += new Array(level).join(blockIndent);
+ }
+ }
+
+ // If found block node stop at the next one for space check
+ // For the pre-block node, find its last (the deepest) child
+ var whitespaceNode = getLastWhitespaceNode(node);
+
+ // If it's spaces, modify this node
+ // If it's something different from spaces, add a space node
+ // to the end
+ if (whitespaceNode) {
+ whitespaceNode.content = value;
+ } else if (value !== '') {
+ var space = gonzales.createNode({
+ type: 'space',
+ content: value
+ });
+ node.content.push(space);
+ }
+
+ processBlock(node, level);
+ });
+ }
+
+
+ return {
+ name: 'space-before-closing-brace',
+
+ runBefore: 'tab-size',
+
+ syntax: ['css', 'less', 'scss'],
+
+ accepts: {
+ number: true,
+ string: /^[ \t\n]*$/
+ },
+
+ /**
+ * Processes tree node.
+ * @param {node} ast
+ * @param {Object} config
+ */
+ process: function(ast, config) {
+ valueFromSettings = this.value;
+ blockIndent = config['block-indent'];
+
+ processBlock(ast);
+ },
+
+ /**
+ * Detects the value of an option at the tree node.
+ *
+ * @param {node} ast
+ */
+ detect: function(ast) {
+ let detected = [];
+
+ ast.traverseByTypes(['block', 'atrulers'], function(node) {
+ // For the block node, find its last (the deepest) child
+ var whitespaceNode = getLastWhitespaceNode(node);
+ if (whitespaceNode) {
+ detected.push(whitespaceNode.content);
+ } else {
+ detected.push('');
+ }
+ });
+
+ return detected;
+ }
+ };
+})();
diff --git a/src/options/space-before-colon.js b/src/options/space-before-colon.js
new file mode 100644
index 00000000..1490bcb3
--- /dev/null
+++ b/src/options/space-before-colon.js
@@ -0,0 +1,65 @@
+'use strict';
+
+var gonzales = require('gonzales-pe');
+
+module.exports = {
+ name: 'space-before-colon',
+
+ runBefore: 'block-indent',
+
+ syntax: ['css', 'less', 'sass', 'scss'],
+
+ accepts: {
+ number: true,
+ string: /^[ \t\n]*$/
+ },
+
+ /**
+ * Processes tree node.
+ *
+ * @param {node} ast
+ */
+ process: function(ast) {
+ let value = this.value;
+
+ ast.traverseByType('propertyDelimiter', function(delimiter, i, parent) {
+ if (delimiter.syntax === 'sass' && !parent.get(i - 1))
+ return;
+
+ // Remove any spaces before colon:
+ if (parent.get(i - 1).is('space')) {
+ parent.removeChild(--i);
+ }
+
+ // If the value set in config is not empty, add spaces:
+ if (value !== '') {
+ var space = gonzales.createNode({
+ type: 'space',
+ content: value
+ });
+ parent.insert(i, space);
+ }
+ });
+ },
+
+ /**
+ * Detects the value of an option at the tree node.
+ *
+ * @param {node} ast
+ */
+ detect: function(ast) {
+ let detected = [];
+
+ ast.traverseByType('propertyDelimiter', function(delimiter, i, parent) {
+ var previousNode = parent.get(i - 1);
+
+ if (previousNode && previousNode.is('space')) {
+ detected.push(previousNode.content);
+ } else {
+ detected.push('');
+ }
+ });
+
+ return detected;
+ }
+};
diff --git a/src/options/space-before-combinator.js b/src/options/space-before-combinator.js
new file mode 100644
index 00000000..6360e6ea
--- /dev/null
+++ b/src/options/space-before-combinator.js
@@ -0,0 +1,62 @@
+'use strict';
+
+var gonzales = require('gonzales-pe');
+
+module.exports = {
+ name: 'space-before-combinator',
+
+ runBefore: 'block-indent',
+
+ syntax: ['css', 'less', 'sass', 'scss'],
+
+ accepts: {
+ number: true,
+ string: /^[ \t\n]*$/
+ },
+
+ /**
+ * Processes tree node.
+ *
+ * @param {node} ast
+ */
+ process: function(ast) {
+ let value = this.value;
+
+ ast.traverseByType('combinator', function(combinator, i, parent) {
+ var previousNode = parent.get(i - 1);
+
+ if (!previousNode) return;
+
+ if (previousNode.is('space')) {
+ previousNode.content = value;
+ } else {
+ var space = gonzales.createNode({
+ type: 'space',
+ content: value
+ });
+ parent.insert(i, space);
+ }
+ });
+ },
+
+ /**
+ * Detects the value of an option at the tree node.
+ *
+ * @param {node} ast
+ */
+ detect: function(ast) {
+ let detected = [];
+
+ ast.traverseByType('combinator', function(combinator, i, parent) {
+ var previousNode = parent.get(i - 1);
+
+ if (previousNode && previousNode.is('space')) {
+ detected.push(previousNode.content);
+ } else {
+ detected.push('');
+ }
+ });
+
+ return detected;
+ }
+};
diff --git a/src/options/space-before-opening-brace.js b/src/options/space-before-opening-brace.js
new file mode 100644
index 00000000..e3eca162
--- /dev/null
+++ b/src/options/space-before-opening-brace.js
@@ -0,0 +1,75 @@
+'use strict';
+
+var gonzales = require('gonzales-pe');
+
+module.exports = (function() {
+ return {
+ name: 'space-before-opening-brace',
+
+ runBefore: 'block-indent',
+
+ syntax: ['css', 'less', 'scss'],
+
+ accepts: {
+ number: true,
+ string: /^[ \t\n]*$/
+ },
+
+ /**
+ * Processes tree node.
+ *
+ * @param {node} ast
+ */
+ process: function(ast) {
+ let value = this.value;
+
+ // If found block node stop at the next one for space check.
+ ast.traverseByTypes(['block', 'value'], function(block, i, parent) {
+ if (block.is('value') && !block.first().is('block')) return;
+
+ var previousNode = parent.get(i - 1);
+ if (!previousNode) return;
+
+ // If it's spaces, modify this node.
+ // If it's something different from spaces, add a space node to
+ // the end:
+ if (previousNode.is('space')) {
+ previousNode.content = value;
+ } else if (value !== '') {
+ var space = gonzales.createNode({
+ type: 'space',
+ content: value
+ });
+ parent.insert(i, space);
+ }
+ });
+ },
+
+ /**
+ * Detects the value of an option at the tree node.
+ *
+ * @param {node} ast
+ */
+ detect: function(ast) {
+ var detected = [];
+
+ ast.traverseByTypes(['block', 'value'], function(block, i, parent) {
+ if (block.is('value') && !block.first().is('block')) return;
+
+ var previousNode = parent.get(i - 1);
+ if (!previousNode) return;
+
+ // If it's spaces, modify this node.
+ // If it's something different from spaces, add a space node to
+ // the end:
+ if (previousNode.is('space')) {
+ detected.push(previousNode.content);
+ } else {
+ detected.push('');
+ }
+ });
+
+ return detected;
+ }
+ };
+})();
diff --git a/src/options/space-before-selector-delimiter.js b/src/options/space-before-selector-delimiter.js
new file mode 100644
index 00000000..29f153e2
--- /dev/null
+++ b/src/options/space-before-selector-delimiter.js
@@ -0,0 +1,64 @@
+'use strict';
+
+var gonzales = require('gonzales-pe');
+
+module.exports = {
+ name: 'space-before-selector-delimiter',
+
+ runBefore: 'block-indent',
+
+ syntax: ['css', 'less', 'sass', 'scss'],
+
+ accepts: {
+ number: true,
+ string: /^[ \t\n]*$/
+ },
+
+ /**
+ * Processes tree node.
+ *
+ * @param {node} ast
+ */
+ process: function(ast) {
+ let value = this.value;
+
+ ast.traverseByType('delimiter', function(delimiter, i, parent) {
+ if (parent.is('arguments')) return;
+
+ var previousNode = parent.get(i - 1);
+
+ if (previousNode && previousNode.is('space')) {
+ previousNode.content = value;
+ } else {
+ var space = gonzales.createNode({
+ type: 'space',
+ content: value
+ });
+ parent.insert(i, space);
+ }
+ });
+ },
+
+ /**
+ * Detects the value of an option at the tree node.
+ *
+ * @param {node} ast
+ */
+ detect: function(ast) {
+ let detected = [];
+
+ ast.traverseByType('delimiter', function(delimiter, i, parent) {
+ if (parent.is('arguments')) return;
+
+ var previousNode = parent.get(i - 1);
+
+ if (previousNode && previousNode.is('space')) {
+ detected.push(previousNode.content);
+ } else {
+ detected.push('');
+ }
+ });
+
+ return detected;
+ }
+};
diff --git a/src/options/space-between-declarations.js b/src/options/space-between-declarations.js
new file mode 100644
index 00000000..2c14e60c
--- /dev/null
+++ b/src/options/space-between-declarations.js
@@ -0,0 +1,97 @@
+'use strict';
+
+var gonzales = require('gonzales-pe');
+
+module.exports = (function() {
+ function getDeclarationEnd(node, i) {
+ for (; i < node.length; i++) {
+ if (!node.get(i + 1) || typeof node.get(i + 1) === 'string') {
+ return 0;
+ } else if (node.get(i + 1).is('space')) {
+ if (node.get(i + 1).content.indexOf('\n') > -1) {
+ if (node.get(i + 2) && node.get(i + 2).is('declaration')) {
+ return i;
+ } else {
+ return 0;
+ }
+ } else if (node.get(i + 2) &&
+ node.get(i + 2).is('multilineComment')) {
+ if (node.get(i + 3) && node.get(i + 3).is('declaration')) {
+ return i + 2;
+ } else if (node.get(i + 3) && node.get(i + 3).is('space')) {
+ if (node.get(i + 4) &&
+ node.get(i + 4).is('declaration')) {
+ return i + 2;
+ } else {
+ return 0;
+ }
+ } else {
+ return 0;
+ }
+ } else if (node.get(i + 2) &&
+ node.get(i + 2).is('declaration')) {
+ return i;
+ }
+ } else if (node.get(i + 1).is('declaration')) {
+ return i;
+ } else if (node.get(i + 1).is('multilineComment')) {
+ if (node.get(i + 2) && node.get(i + 2).is('declaration')) {
+ return i + 1;
+ } else if (node.get(i + 2) && node.get(i + 2).is('space')) {
+ if (node.get(i + 3) && node.get(i + 3).is('declaration')) {
+ return i + 1;
+ }
+ } else {
+ return 0;
+ }
+ } else {
+ return 0;
+ }
+ }
+ }
+
+ return {
+ name: 'space-between-declarations',
+
+ runBefore: 'block-indent',
+
+ syntax: ['css', 'less', 'scss'],
+
+ accepts: {
+ number: true,
+ string: /^[ \t\n]*$/
+ },
+
+ /**
+ * Processes tree node.
+ *
+ * @param {node} ast
+ */
+ process: function(ast) {
+ let value = this.value;
+
+ ast.traverseByType('declarationDelimiter', (delimiter, i, parent) => {
+ // Grom user's point of view "declaration" includes semicolons
+ // and comments placed on the same line.
+ // So group those things together:
+ var declarationEnd = getDeclarationEnd(parent, i);
+ if (!declarationEnd) {
+ return;
+ } else {
+ i = declarationEnd;
+ }
+
+ var nextNode = parent.get(i + 1);
+ if (nextNode && nextNode.is('space')) {
+ nextNode.content = value;
+ } else {
+ var space = gonzales.createNode({
+ type: 'space',
+ content: value
+ });
+ parent.insert(i + 1, space);
+ }
+ });
+ }
+ };
+})();
diff --git a/src/options/strip-spaces.js b/src/options/strip-spaces.js
new file mode 100644
index 00000000..50ea4ae9
--- /dev/null
+++ b/src/options/strip-spaces.js
@@ -0,0 +1,66 @@
+'use strict';
+
+module.exports = (function() {
+ /**
+ * Trim trailing spaces on each line.
+ * @private
+ * @param {String} string Spaceful string
+ * @returns {String}
+ */
+ function trim(string) {
+ return string.replace(/[ \t]+\n/g, '\n');
+ }
+
+ return {
+ name: 'strip-spaces',
+
+ syntax: ['css', 'less', 'sass', 'scss'],
+
+ accepts: {
+ boolean: [true]
+ },
+
+ /**
+ * Processes tree node.
+ * @param {node} ast
+ */
+ process: function(ast) {
+ var lastChild = ast.last();
+ if (lastChild.is('space')) {
+ lastChild.content = trim(lastChild.content)
+ .replace(/[ \t]+$/, '')
+ .replace(/[\n]+/g, '\n');
+ }
+
+ ast.traverseByType('space', function(space) {
+ space.content = trim(space.content);
+ });
+ },
+
+ detectDefault: true,
+
+ /**
+ * Detects the value of an option at the tree node.
+ * This option is treated as `true` by default, but any trailing
+ * space would invalidate it.
+ *
+ * @param {node} ast
+ */
+ detect: function(ast) {
+ let detected = [];
+
+ var lastChild = ast.last();
+ if (lastChild.is('space') &&
+ lastChild.content !== '\n' &&
+ lastChild.content.match(/^[ \n\t]+$/)) {
+ detected.push(false);
+ }
+
+ ast.traverseByType('space', function(space) {
+ if (space.content.match(/[ \t]\n/)) detected.push(false);
+ });
+
+ return detected;
+ }
+ };
+})();
diff --git a/src/options/tab-size.js b/src/options/tab-size.js
new file mode 100644
index 00000000..d9a4b753
--- /dev/null
+++ b/src/options/tab-size.js
@@ -0,0 +1,26 @@
+'use strict';
+
+module.exports = {
+ name: 'tab-size',
+
+ runBefore: 'vendor-prefix-align',
+
+ syntax: ['css', 'less', 'sass', 'scss'],
+
+ accepts: {
+ number: true
+ },
+
+ /**
+ * Processes tree node.
+ *
+ * @param {node} ast
+ */
+ process: function(ast) {
+ let value = this.value;
+
+ ast.traverseByType('space', function(space) {
+ space.content = space.content.replace(/\t/, value);
+ });
+ }
+};
diff --git a/src/options/unitless-zero.js b/src/options/unitless-zero.js
new file mode 100644
index 00000000..467db682
--- /dev/null
+++ b/src/options/unitless-zero.js
@@ -0,0 +1,77 @@
+'use strict';
+
+module.exports = {
+ name: 'unitless-zero',
+
+ syntax: ['css', 'less', 'sass', 'scss'],
+
+ accepts: {
+ boolean: [true]
+ },
+
+ /**
+ * Processes tree node.
+ *
+ * @param {node} ast
+ */
+ process: function(ast) {
+ var UNITS = ['cm', 'em', 'ex', 'pt', 'px'];
+
+ ast.traverseByTypes(['value', 'parentheses'], function(node) {
+ node.forEach(function(value) {
+ if (typeof value === 'string') return;
+
+ if (value.is('dimension')) {
+ var unit = value.first('ident').content;
+ if (value.first('number').content === '0' &&
+ UNITS.indexOf(unit) !== -1) {
+ value.removeChild(1);
+ }
+ } else if (value.is('percentage')) {
+ var number = value.first('number').content;
+ if (number === '0') {
+ value.type = 'number';
+ value.content = number;
+ }
+ }
+ });
+ });
+ },
+
+ /**
+ * Detects the value of an option at the tree node.
+ *
+ * @param {node} ast
+ */
+ detect: function(ast) {
+ let detected = [];
+
+ ast.traverse(function(node, index, parent) {
+ // If we see a zero with unit and it is not degree,
+ // then we don’t have an option
+ if (node.is('percentage') &&
+ node.first('number').content[1] === '0') {
+ detected.push(false);
+ return;
+ }
+
+ if (node.is('dimension') &&
+ node.first('number').content === '0' &&
+ node.first('ident').content !== 'deg') {
+ detected.push(false);
+ return;
+ }
+
+ // If we see a zero and previous node is not percentage
+ // or dimension, then we have an option
+ if (node.is('number') &&
+ node.content === '0' &&
+ !parent.is('percentage') &&
+ !parent.is('dimension')) {
+ detected.push(true);
+ }
+ });
+
+ return detected;
+ }
+};
diff --git a/src/options/vendor-prefix-align.js b/src/options/vendor-prefix-align.js
new file mode 100644
index 00000000..a816340e
--- /dev/null
+++ b/src/options/vendor-prefix-align.js
@@ -0,0 +1,474 @@
+'use strict';
+
+var gonzales = require('gonzales-pe');
+
+module.exports = (function() {
+ // Vendor prefixes list:
+ var PREFIXES = [
+ 'webkit',
+ 'khtml',
+ 'moz',
+ 'ms',
+ 'o'
+ ];
+
+ var oneline;
+
+ /**
+ * Makes namespace from property name.
+ *
+ * @param {String} propertyName
+ * @returns {String|undefined}
+ */
+ function makeNamespace(propertyName) {
+ var info = getPrefixInfo(propertyName);
+ return info && info.baseName;
+ }
+
+ /**
+ * Creates object which contains info about vendor prefix used
+ * in propertyName.
+ *
+ * @param {String} propertyName property name
+ * @param {String} [namespace=''] namespace name
+ * @param {Number} [extraSymbols=0] extra symbols count
+ * @returns {Object|undefined}
+ */
+ function getPrefixInfo(propertyName, namespace, extraSymbols) {
+ var baseName = propertyName;
+ var prefixLength = 0;
+
+ namespace = namespace || '';
+ extraSymbols = extraSymbols || 0;
+
+ if (!propertyName) return;
+
+ PREFIXES.some(function(prefix) {
+ prefix = '-' + prefix + '-';
+ if (propertyName.indexOf(prefix) !== 0) return;
+
+ baseName = baseName.substr(prefix.length);
+ prefixLength = prefix.length;
+
+ return true;
+ });
+
+ return {
+ id: namespace + baseName,
+ baseName: baseName,
+ prefixLength: prefixLength,
+ extra: extraSymbols
+ };
+ }
+
+ /**
+ * Returns extra indent for item in arguments
+ *
+ * @param {Array} nodes nodes to process
+ * @returns {Number|undefined}
+ */
+ function extraIndent(nodes) {
+ if (!nodes || !nodes.length) return;
+
+ var node;
+ var crPos;
+ var tabPos;
+ var result = 0;
+
+ for (var i = nodes.length; i--;) {
+ node = nodes[i];
+
+ if (!node.content) {
+ crPos = -1;
+ } else {
+ crPos = node.content.lastIndexOf('\n');
+ tabPos = node.content.lastIndexOf('\t');
+ if (tabPos > crPos) crPos = tabPos;
+ }
+
+ if (crPos !== -1)
+ oneline = false;
+
+ if (node.is('space')) {
+ result += node.content.length - crPos - 1;
+ if (crPos !== -1)
+ break;
+ }
+ if (node.is('multilineComment')) {
+ if (crPos === -1) {
+ // Comment symbols length
+ let offset = 4;
+ result += node.content.length + offset;
+ } else {
+ // Only last comment symbols length - 1 (not count \n)
+ let offset = crPos - 1;
+ result += node.content.length - offset;
+ break;
+ }
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Wrapper for extra indent function for `property` node.
+ *
+ * @param {Array} nodes all nodes
+ * @param {Number} i position in nodes array
+ */
+ function extraIndentProperty(nodes, i) {
+ var subset = [];
+ while (i--) {
+ if (!nodes.get(i) || nodes.get(i).is('declarationDelimiter'))
+ break;
+ subset.unshift(nodes.get(i));
+ }
+ return extraIndent(subset);
+ }
+
+ /**
+ * Wrapper for extra indent function for val-node.
+ *
+ * @param {Array} nodes all nodes
+ * @param {Number} i position in nodes array
+ */
+ function extraIndentVal(nodes, i) {
+ var subset = [];
+ var declaration = nodes.get(i);
+ if (!declaration.is('declaration')) return;
+
+ for (var x = declaration.length; x--;) {
+ if (!declaration.get(x).is('value')) continue;
+
+ x--;
+
+ while (!declaration.get(x).is('propertyDelimiter')) {
+ subset.push(declaration.get(x));
+ x--;
+ }
+
+ break;
+ }
+ return extraIndent(subset);
+ }
+
+ /**
+ * Walks across nodes, and call payload for every node that pass
+ * selector check.
+ *
+ * @param {Object} args arguments in form of:
+ * {
+ * node: {object} current node,
+ * selector: {function} propertyName selector
+ * payload: {function} work to do with gathered info
+ * namespaceSelector: {function} selector for namespace
+ * getExtraSymbols: {Number} extra symbols count
+ * }
+ */
+ function walk(args) {
+ args.node.forEach(function(item, i) {
+ var name = args.selector(item);
+ var namespace = args.namespaceSelector &&
+ makeNamespace(args.namespaceSelector(item));
+ var extraSymbols = args.getExtraSymbols(args.node, i);
+
+ var info = name && getPrefixInfo(name, namespace, extraSymbols);
+ if (!info) return;
+ args.payload(info, i);
+ });
+ }
+
+ /**
+ * Returns property name.
+ * e.g.
+ * for: 'color: #fff'
+ * returns string: 'color'
+ *
+ * @param {node} node
+ * @returns {String|undefined}
+ */
+ function getPropertyName(node) {
+ if (!node.is('declaration')) return;
+ // TODO: Check that it's not a variable
+ return node.get(0).get(0).content;
+ }
+
+ /**
+ * Returns property value name.
+ * e.g.
+ * for: '-webkit-transition: -webkit-transform 150ms linear'
+ * returns string: '-webkit-transform', and
+ * for: 'background: -webkit-linear-gradient(...)'
+ * returns string: '-webkit-linear-gradient'
+ *
+ * @param {node} node
+ * @returns {String|undefined}
+ */
+ function getValName(node) {
+ if (!node.is('declaration')) return;
+
+ var value = node.first('value');
+ if (value.get(0).is('ident')) return value.get(0).content;
+ if (value.get(0).is('function')) return value.get(0).get(0).content;
+ }
+
+ /**
+ * Updates dict which contains info about items align.
+ *
+ * @param {Object} info
+ * @param {Object} dict
+ */
+ function updateDict(info, dict) {
+ if (info.prefixLength === 0 && info.extra === 0) return;
+
+ var indent = dict[info.id] || {prefixLength: 0, extra: 0};
+
+ let indentLength = indent.prefixLength + indent.extra;
+ let infoLength = info.prefixLength + info.extra;
+ if (indentLength > infoLength) {
+ dict[info.id] = indent;
+ } else {
+ dict[info.id] = {
+ prefixLength: info.prefixLength,
+ extra: info.extra
+ };
+ }
+ }
+
+ /**
+ * Returns string with correct number of spaces for info.baseName property.
+ *
+ * @param {Object} info
+ * @param {Object} dict
+ * @param {String} whitespaceNode
+ * @returns {String}
+ */
+ function updateIndent(info, dict, whitespaceNode) {
+ var item = dict[info.id];
+ if (!item)
+ return whitespaceNode;
+
+ var crPos = whitespaceNode.lastIndexOf('\n');
+ var tabPos = whitespaceNode.lastIndexOf('\t');
+ if (tabPos > crPos) crPos = tabPos;
+
+ var firstPart = whitespaceNode.substr(0, crPos + 1);
+ var extraIndent = new Array(
+ (item.prefixLength - info.prefixLength) +
+ (item.extra - info.extra) +
+ whitespaceNode.length - firstPart.length +
+ 1).join(' ');
+
+ return firstPart.concat(extraIndent);
+ }
+
+ return {
+ name: 'vendor-prefix-align',
+
+ syntax: ['css', 'less', 'sass', 'scss'],
+
+ accepts: {
+ boolean: [true]
+ },
+
+ /**
+ * Processes tree node.
+ *
+ * @param {node} ast
+ */
+ process: function(ast) {
+ ast.traverseByType('block', function(node) {
+ oneline = true;
+
+ var dict = {};
+
+ // Gathering Info
+ walk({
+ node: node,
+ selector: getPropertyName,
+ getExtraSymbols: extraIndentProperty,
+ payload: function(info) {
+ updateDict(info, dict);
+ }
+ });
+
+ walk({
+ node: node,
+ selector: getValName,
+ namespaceSelector: getPropertyName,
+ getExtraSymbols: extraIndentVal,
+ payload: function(info) {
+ updateDict(info, dict);
+ }
+ });
+
+ if (oneline && ast.syntax !== 'sass') return;
+
+ // Update nodes
+ walk({
+ node: node,
+ selector: getValName,
+ namespaceSelector: getPropertyName,
+ getExtraSymbols: extraIndentVal,
+ payload: function(info, i) {
+ for (var x = node.get(i).length; x--;) {
+ if (node.get(i).get(x).is('value')) break;
+ }
+
+ let prevNode = node.get(i).get(x - 1);
+ if (!prevNode.is('space')) {
+ var space = gonzales.createNode({
+ type: 'space',
+ content: ''
+ });
+ node.get(i).insert(x, space);
+ ++x;
+ }
+
+ let content = node.get(i).get(x - 1).content;
+ let updatedIndent = updateIndent(info, dict, content);
+ node.get(i).get(x - 1).content = updatedIndent;
+ }
+ });
+
+ if (ast.syntax === 'sass') return;
+
+ walk({
+ node: node,
+ selector: getPropertyName,
+ getExtraSymbols: extraIndentProperty,
+ payload: function(info, i) {
+ // `node.get(i - 1)` can be either space or comment:
+ var whitespaceNode = node.get(i - 1);
+ if (!whitespaceNode) return;
+ // If it's a comment, insert an empty space node:
+ if (!whitespaceNode.is('space')) {
+ whitespaceNode = gonzales.createNode({
+ type: 'space',
+ content: ''
+ });
+ node.insert(i - 1, whitespaceNode);
+ }
+ let content = whitespaceNode.content;
+ let updatedContent = updateIndent(info, dict, content);
+ whitespaceNode.content = updatedContent;
+ }
+ });
+ });
+ },
+
+ /**
+ * Detects the value of an option at the tree node.
+ *
+ * @param {node} ast
+ */
+ detect: function(ast) {
+ let detected = [];
+
+ ast.traverseByType('block', function(node) {
+ var result = {
+ true: 0,
+ false: 0
+ };
+
+ var maybePrefix = false;
+ var prevPrefixLength = false;
+ var prevProp;
+ var prevSum;
+ var partialResult = null;
+
+ var getResult = function(options) {
+ let {node, sum, info, i} = options;
+ var prop = info.baseName;
+
+ // If this is the last item in a row and we have a result,
+ // then catch it
+ if (prop !== prevProp && partialResult !== null) {
+ if (partialResult) {
+ result.true++;
+ } else {
+ result.false++;
+ }
+ partialResult = null;
+ }
+
+ if (prop === prevProp &&
+ info.prefixLength !== prevPrefixLength) {
+ maybePrefix = true;
+ } else {
+ maybePrefix = false;
+ }
+
+ if (maybePrefix && partialResult !== false) {
+ // If there is prefixed prop, check if the prefixes are
+ // aligned, but only if we hadn't already catched
+ // that it is false
+ if (sum === prevSum) {
+ partialResult = true;
+ } else {
+ partialResult = false;
+ }
+ }
+
+ if (node.length === i + 3 && partialResult !== null) {
+ // If we're at the last property and have a result,
+ // catch it
+ if (partialResult) {
+ result.true++;
+ } else {
+ result.false++;
+ }
+ }
+
+ prevPrefixLength = info.prefixLength;
+ prevProp = prop;
+ prevSum = sum;
+ };
+
+ // Gathering Info
+ walk({
+ node: node,
+ selector: getPropertyName,
+ getExtraSymbols: extraIndentProperty,
+ payload: function(info, i) {
+ if (node.get(i - 1) && node.get(i - 1).content) {
+ let nodeLength = node.get(i - 1).content
+ .replace(/^[ \t]*\n+/, '').length;
+ var sum = nodeLength + info.prefixLength;
+ getResult({node: node, sum: sum, info: info, i: i});
+ }
+ }
+ });
+
+ walk({
+ node: node,
+ selector: getValName,
+ getExtraSymbols: extraIndentVal,
+ payload: function(info, i) {
+ for (var x = node.get(i).length; x--;) {
+ if (node.get(i).get(x).is('value')) break;
+ }
+
+ if (node.get(i).get(x - 1)) {
+ let nodeLength = node.get(i).get(x - 1).content
+ .replace(/^[ \t]*\n+/, '').length;
+ var sum = nodeLength + info.prefixLength;
+ getResult({node: node, sum: sum, info: info, i: i});
+ }
+ }
+ });
+
+ if (result.true > 0 || result.false > 0) {
+ if (result.true >= result.false) {
+ detected.push(true);
+ } else {
+ detected.push(false);
+ }
+ }
+ });
+
+ return detected;
+ }
+ };
+})();
diff --git a/src/plugin.js b/src/plugin.js
new file mode 100644
index 00000000..a08046f8
--- /dev/null
+++ b/src/plugin.js
@@ -0,0 +1,96 @@
+'use strict';
+
+
+let Errors = require('./errors');
+
+let Plugin = function(methods) {
+ for (let method in methods) {
+ this[method] = typeof method === 'function'?
+ methods[method].bind(this) : methods[method];
+ }
+
+ this.validate();
+};
+
+Plugin.prototype = {
+ /**
+ * Plugin's name.
+ * @type {String}
+ */
+ name: null,
+
+ /**
+ * List of supported syntaxes.
+ * @type {Array}
+ */
+ syntax: null,
+
+ /**
+ * @type {Object}
+ */
+ accepts: null,
+
+ /**
+ * @type {Function}
+ */
+ process: null,
+
+ /**
+ * @type {Function}
+ */
+ lint: null,
+
+ value_: null,
+ get value() {
+ return this.value_;
+ },
+ set value(value) {
+ let valueType = typeof value;
+ let pattern = this.accepts && this.accepts[valueType];
+
+ if (this.setValue) {
+ this.value_ = this.setValue(value);
+ return this.value_;
+ }
+
+ if (!pattern)
+ throw new Error(Errors.unacceptableValueType(valueType, this.accepts));
+
+ if (valueType === 'boolean') {
+ if (pattern.indexOf(value) < 0)
+ throw new Error(Errors.unacceptableBoolean(pattern));
+ this.value_ = value;
+ return this.value_;
+ }
+
+ if (valueType === 'number') {
+ if (value !== parseInt(value))
+ throw new Error(Errors.unacceptableNumber());
+ this.value_ = new Array(value + 1).join(' ');
+ return this.value_;
+ }
+
+ if (valueType = 'string') {
+ if (!value.match(pattern))
+ throw new Error(Errors.unacceptableString(pattern));
+ this.value_ = value;
+ return this.value_;
+ }
+
+ throw new Error(Errors.implementSetValue(valueType));
+ },
+
+ validate() {
+ if (typeof this.name !== 'string' || !this.name)
+ throw new Error(Errors.missingName());
+
+ if (!Array.isArray(this.syntax) || this.syntax.length === 0)
+ throw new Error(Errors.missingSyntax());
+
+ if (typeof this.accepts !== 'object' &&
+ typeof this.setValue !== 'function')
+ throw new Error(Errors.missingSetValue());
+ }
+};
+
+module.exports = Plugin;
diff --git a/test/always-semicolon.js b/test/always-semicolon.js
deleted file mode 100644
index e6060614..00000000
--- a/test/always-semicolon.js
+++ /dev/null
@@ -1,60 +0,0 @@
-var Comb = require('../lib/csscomb');
-var assert = require('assert');
-
-describe('options/always-semicolon', function() {
- var comb;
-
- beforeEach(function() {
- comb = new Comb();
- });
-
- it('Should add semicolon for last property if missing. Test 1', function() {
- comb.configure({ 'always-semicolon': true });
- assert.equal(
- comb.processString(
- 'div { height: 0 }'
- ),
- 'div { height: 0; }'
- );
- });
-
- it('Should add semicolon for last property if missing. Test 2', function() {
- comb.configure({ 'always-semicolon': true });
- assert.equal(
- comb.processString(
- 'div {\nheight: 0\n}'
- ),
- 'div {\nheight: 0;\n}'
- );
- });
-
- it('Should add semicolon for last property if missing. Test 3', function() {
- comb.configure({ 'always-semicolon': true });
- assert.equal(
- comb.processString(
- 'div {height: 0}'
- ),
- 'div {height: 0;}'
- );
- });
-
- it('Should add semicolon for last property if missing. Test 4', function() {
- comb.configure({ 'always-semicolon': true });
- assert.equal(
- comb.processString(
- 'div {\nheight: 0 /* Comment */\n}'
- ),
- 'div {\nheight: 0; /* Comment */\n}'
- );
- });
-
- it('Should add semicolon for last property if missing. Test 5', function() {
- comb.configure({ 'always-semicolon': true });
- assert.equal(
- comb.processString(
- 'div {\ntop: 1px;\nheight: 0 /* 1comment */ /* 2comment */\n}'
- ),
- 'div {\ntop: 1px;\nheight: 0; /* 1comment */ /* 2comment */\n}'
- );
- });
-});
diff --git a/test/block-indent.js b/test/block-indent.js
deleted file mode 100644
index f5a08d57..00000000
--- a/test/block-indent.js
+++ /dev/null
@@ -1,44 +0,0 @@
-var Comb = require('../lib/csscomb');
-var assert = require('assert');
-
-describe('options/block-indent', function() {
- var comb;
- beforeEach(function() {
- comb = new Comb();
- });
- it('Invalid Number value should not change space after brace', function() {
- comb.configure({ 'block-indent': 3.5 });
- assert.equal(
- comb.processString('a { color: red }'),
- 'a { color: red }'
- );
- });
- it('Invalid String value should not change space after brace', function() {
- comb.configure({ 'block-indent': 'foobar' });
- assert.equal(
- comb.processString('a { color: red }'),
- 'a { color: red }'
- );
- });
- it('True Boolean value should set 4 spaces indent', function() {
- comb.configure({ 'block-indent': true });
- assert.equal(
- comb.processString(' \n a { color: red } @media all {.input__control { color: #000;\n}\n}'),
- ' \na { color: red \n}\n@media all {\n .input__control { color: #000;\n }\n}'
- );
- });
- it('Valid Number value should set equal space after brace', function() {
- comb.configure({ 'block-indent': 3 });
- assert.equal(
- comb.processString(' a\n{ color: red } @media all { .input__control\n{ color: #000;\n}\n}'),
- 'a\n{ color: red \n}\n@media all {\n .input__control\n { color: #000;\n }\n}'
- );
- });
- it('Valid String value should set equal space after brace', function() {
- comb.configure({ 'block-indent': '\t' });
- assert.equal(
- comb.processString(' a { color: red } @media all { .input__control\n{ color: #000;\n}\n}'),
- 'a { color: red \n}\n@media all {\n\t.input__control\n\t{ color: #000;\n\t}\n}'
- );
- });
-});
diff --git a/test/colon-space.js b/test/colon-space.js
deleted file mode 100644
index 6514bb78..00000000
--- a/test/colon-space.js
+++ /dev/null
@@ -1,149 +0,0 @@
-var Comb = require('../lib/csscomb');
-var assert = require('assert');
-
-describe('options/colon-space', function() {
- var comb;
- beforeEach(function() {
- comb = new Comb();
- });
- it('Invalid String should not change space around colon', function() {
- comb.configure({ 'colon-space': 'foobar' });
- assert.equal(
- comb.processString('a { color : red }'),
- 'a { color : red }'
- );
- });
- it('True Boolean value should set space after colon', function() {
- comb.configure({ 'colon-space': true });
- assert.equal(
- comb.processString(
- 'a { color: red }' +
- 'a{color:red}' +
- 'a {color : red}' +
- 'a {color : /* foo */ red }' +
- 'a {color /* bar */ : red }'
- ),
- 'a { color: red }' +
- 'a{color: red}' +
- 'a {color: red}' +
- 'a {color: /* foo */ red }' +
- 'a {color /* bar */: red }'
- );
- });
- it('False Boolean value should set no space around colon', function() {
- comb.configure({ 'colon-space': false });
- assert.equal(
- comb.processString(
- 'a { color: red }' +
- 'a{color:red}' +
- 'a {color : red}' +
- 'a {color : /* foo */ red }' +
- 'a {color /* bar */ : red }'
- ),
- 'a { color:red }' +
- 'a{color:red}' +
- 'a {color:red}' +
- 'a {color:/* foo */ red }' +
- 'a {color /* bar */:red }'
- );
- });
- it('String `after` value should set space after colon', function() {
- comb.configure({ 'colon-space': 'after' });
- assert.equal(
- comb.processString(
- 'a { color: red }' +
- 'a{color:red}' +
- 'a {color : red}' +
- 'a {color : /* foo */ red }' +
- 'a {color /* bar */ : red }'
- ),
- 'a { color: red }' +
- 'a{color: red}' +
- 'a {color: red}' +
- 'a {color: /* foo */ red }' +
- 'a {color /* bar */: red }'
- );
- });
- it('String `before` value should set space before colon', function() {
- comb.configure({ 'colon-space': 'before' });
- assert.equal(
- comb.processString(
- 'a { color: red }' +
- 'a{color:red}' +
- 'a {color : red}' +
- 'a {color : /* foo */ red }' +
- 'a {color /* bar */ : red }'
- ),
- 'a { color :red }' +
- 'a{color :red}' +
- 'a {color :red}' +
- 'a {color :/* foo */ red }' +
- 'a {color /* bar */ :red }'
- );
- });
- it('String `both` value should set spaces around colon', function() {
- comb.configure({ 'colon-space': 'both' });
- assert.equal(
- comb.processString(
- 'a { color: red }' +
- 'a{color:red}' +
- 'a {color : red}'
- ),
- 'a { color : red }' +
- 'a{color : red}' +
- 'a {color : red}'
- );
- });
- it('String ` ` value should set two spaces after colon', function() {
- comb.configure({ 'colon-space': ' ' });
- assert.equal(
- comb.processString(
- 'a { color: red }' +
- 'a{color:red}' +
- 'a {color : red}'
- ),
- 'a { color: red }' +
- 'a{color: red}' +
- 'a {color: red}'
- );
- });
- it('String `:` value should set no space around colon', function() {
- comb.configure({ 'colon-space': ':' });
- assert.equal(
- comb.processString(
- 'a { color: red }' +
- 'a{color:red}' +
- 'a {color : red}'
- ),
- 'a { color:red }' +
- 'a{color:red}' +
- 'a {color:red}'
- );
- });
- it('String `` value should set no space around colon', function() {
- comb.configure({ 'colon-space': '' });
- assert.equal(
- comb.processString(
- 'a { color: red }' +
- 'a{color:red}' +
- 'a {color : red}'
- ),
- 'a { color:red }' +
- 'a{color:red}' +
- 'a {color:red}'
- );
- });
- it('String `\\t:\\t` value should set tabs around colon', function() {
- comb.configure({ 'colon-space': '\t:\t' });
- assert.equal(
- comb.processString(
- 'a { color: red }' +
- 'a{color:red}' +
- 'a {color : red}'
- ),
- 'a { color\t:\tred }' +
- 'a{color\t:\tred}' +
- 'a {color\t:\tred}'
- );
- });
-});
diff --git a/test/color-case.js b/test/color-case.js
deleted file mode 100644
index 01a7f0e6..00000000
--- a/test/color-case.js
+++ /dev/null
@@ -1,53 +0,0 @@
-var Comb = require('../lib/csscomb');
-var assert = require('assert');
-
-describe('options/color-case', function() {
- var comb;
-
- beforeEach(function() {
- comb = new Comb();
- });
-
- it('Should switch colors to upper case', function() {
- comb.configure({ 'color-case': 'upper' });
- assert.equal(
- comb.processString(
- 'div { color: #fff }'
- ),
- 'div { color: #FFF }'
- );
- });
-
- it('Should switch colors to lower case', function() {
- comb.configure({ 'color-case': 'lower' });
- assert.equal(
- comb.processString(
- 'div { color: #FFF }'
- ),
- 'div { color: #fff }'
- );
- });
-
- it('Should switch color-case in complex rules', function() {
- comb.configure({ 'color-case': 'lower' });
- assert.equal(
- comb.processString(
- 'div { background: url(img.png#RND) #E3E3E3 0 100% no-repeat;' +
- ' box-shadow: 1px 2px 3px 4px #F0F0F0 inset; }'
- ),
- 'div { background: url(img.png#RND) #e3e3e3 0 100% no-repeat;' +
- ' box-shadow: 1px 2px 3px 4px #f0f0f0 inset; }'
- );
- });
-
- it('Should not switch selector case', function() {
- comb.configure({ 'color-case': 'lower' });
- assert.equal(
- comb.processString(
- '#Header { color: #FFF }'
- ),
- '#Header { color: #fff }'
- );
- });
-
-});
diff --git a/test/color-shorthand.js b/test/color-shorthand.js
deleted file mode 100644
index 19458b0a..00000000
--- a/test/color-shorthand.js
+++ /dev/null
@@ -1,41 +0,0 @@
-var Comb = require('../lib/csscomb');
-var assert = require('assert');
-
-describe('options/color-shorthand', function() {
- var comb;
-
- beforeEach(function() {
- comb = new Comb();
- });
-
- it('Should shrink hexadecimal colors to 3 symbols', function() {
- comb.configure({ 'color-shorthand': true });
- assert.equal(
- comb.processString(
- 'div { color: #aabbcc }'
- ),
- 'div { color: #abc }'
- );
- });
-
- it('Should expand hexadecimal colors to 6 symbols', function() {
- comb.configure({ 'color-shorthand': false });
- assert.equal(
- comb.processString(
- 'div { color: #7ad }'
- ),
- 'div { color: #77aadd }'
- );
- });
-
- it('Should save case while processing', function() {
- comb.configure({ 'color-shorthand': true });
- assert.equal(
- comb.processString(
- 'div { color: #fFAafF }'
- ),
- 'div { color: #fAf }'
- );
- });
-
-});
diff --git a/test/combinator-space.js b/test/combinator-space.js
deleted file mode 100644
index 7c269413..00000000
--- a/test/combinator-space.js
+++ /dev/null
@@ -1,157 +0,0 @@
-var Comb = require('../lib/csscomb');
-var assert = require('assert');
-
-describe('options/combinator-space', function() {
- var comb;
- beforeEach(function() {
- comb = new Comb();
- });
- it('Invalid String should not change space around combinator', function() {
- comb.configure({ 'combinator-space': 'foobar' });
- assert.equal(
- comb.processString(
- 'a >b { color: red }' +
- 'a ~b { color: red }' +
- 'a +b { color: red }'
- ),
- 'a >b { color: red }' +
- 'a ~b { color: red }' +
- 'a +b { color: red }'
- );
- });
- it('True Boolean value should set space around combinator to one space', function() {
- comb.configure({ 'combinator-space': true });
- assert.equal(
- comb.processString(
- 'a>b { color: red }' +
- 'a> b { color: red }' +
- 'a >b { color: red }' +
- 'a+b { color: red }' +
- 'a+ b { color: red }' +
- 'a +b { color: red }' +
- 'a~b { color: red }' +
- 'a~ b { color: red }' +
- 'a ~b { color: red }' +
- 'a ~b+ c>d { color: red }'
- ),
- 'a > b { color: red }' +
- 'a > b { color: red }' +
- 'a > b { color: red }' +
- 'a + b { color: red }' +
- 'a + b { color: red }' +
- 'a + b { color: red }' +
- 'a ~ b { color: red }' +
- 'a ~ b { color: red }' +
- 'a ~ b { color: red }' +
- 'a ~ b + c > d { color: red }'
- );
- });
- it('False Boolean value should remove spaces around combinator', function() {
- comb.configure({ 'combinator-space': false });
- assert.equal(
- comb.processString(
- 'a>b { color: red }' +
- 'a> b { color: red }' +
- 'a >b { color: red }' +
- 'a+b { color: red }' +
- 'a+ b { color: red }' +
- 'a +b { color: red }' +
- 'a~b { color: red }' +
- 'a~ b { color: red }' +
- 'a ~b { color: red }' +
- 'a ~b+ c>d { color: red }'
- ),
- 'a>b { color: red }' +
- 'a>b { color: red }' +
- 'a>b { color: red }' +
- 'a+b { color: red }' +
- 'a+b { color: red }' +
- 'a+b { color: red }' +
- 'a~b { color: red }' +
- 'a~b { color: red }' +
- 'a~b { color: red }' +
- 'a~b+c>d { color: red }'
- );
- });
- it('String `` value should remove spaces around combinator', function() {
- comb.configure({ 'combinator-space': '' });
- assert.equal(
- comb.processString(
- 'a>b { color: red }' +
- 'a> b { color: red }' +
- 'a >b { color: red }' +
- 'a+b { color: red }' +
- 'a+ b { color: red }' +
- 'a +b { color: red }' +
- 'a~b { color: red }' +
- 'a~ b { color: red }' +
- 'a ~b { color: red }' +
- 'a ~b+ c>d { color: red }'
- ),
- 'a>b { color: red }' +
- 'a>b { color: red }' +
- 'a>b { color: red }' +
- 'a+b { color: red }' +
- 'a+b { color: red }' +
- 'a+b { color: red }' +
- 'a~b { color: red }' +
- 'a~b { color: red }' +
- 'a~b { color: red }' +
- 'a~b+c>d { color: red }'
- );
- });
- it('String ` ` value should set two spaces around combinator', function() {
- comb.configure({ 'combinator-space': ' ' });
- assert.equal(
- comb.processString(
- 'a>b { color: red }' +
- 'a> b { color: red }' +
- 'a >b { color: red }' +
- 'a+b { color: red }' +
- 'a+ b { color: red }' +
- 'a +b { color: red }' +
- 'a~b { color: red }' +
- 'a~ b { color: red }' +
- 'a ~b { color: red }' +
- 'a ~b+ c>d { color: red }'
- ),
- 'a > b { color: red }' +
- 'a > b { color: red }' +
- 'a > b { color: red }' +
- 'a + b { color: red }' +
- 'a + b { color: red }' +
- 'a + b { color: red }' +
- 'a ~ b { color: red }' +
- 'a ~ b { color: red }' +
- 'a ~ b { color: red }' +
- 'a ~ b + c > d { color: red }'
- );
- });
- it('Array value should set different spaces around combinator', function() {
- comb.configure({ 'combinator-space': [' ', '\n'] });
- assert.equal(
- comb.processString(
- 'a>b { color: red }' +
- 'a> b { color: red }' +
- 'a >b { color: red }' +
- 'a+b { color: red }' +
- 'a+ b { color: red }' +
- 'a +b { color: red }' +
- 'a~b { color: red }' +
- 'a~ b { color: red }' +
- 'a ~b { color: red }' +
- 'a ~b+ c>d { color: red }'
- ),
- 'a >\nb { color: red }' +
- 'a >\nb { color: red }' +
- 'a >\nb { color: red }' +
- 'a +\nb { color: red }' +
- 'a +\nb { color: red }' +
- 'a +\nb { color: red }' +
- 'a ~\nb { color: red }' +
- 'a ~\nb { color: red }' +
- 'a ~\nb { color: red }' +
- 'a ~\nb +\nc >\nd { color: red }'
- );
- });
-});
diff --git a/test/core/configure/test.js b/test/core/configure/test.js
new file mode 100644
index 00000000..4743df40
--- /dev/null
+++ b/test/core/configure/test.js
@@ -0,0 +1,47 @@
+let Comb = process.env.TEST_COV ?
+ require('../../../lib-cov/csscomb') :
+ require('../../../lib/csscomb');
+let assert = require('assert');
+
+describe('CSScomb#configure', function() {
+ it('Passing no config to constructor should not configure anything', function() {
+ let comb = new Comb();
+ assert.equal(undefined, comb._handlers);
+ });
+
+ it('Passing valid config name to constructor should configure using correct config', function() {
+ let comb = new Comb('zen');
+ let input = 'a { color: tomato; top: 0; }';
+ let expected = 'a {top: 0; color: tomato; }';
+ return comb.processString(input).then(function(output) {
+ assert.equal(expected, output);
+ });
+ });
+
+ it('Passing config object to constructor should configure using that object', function() {
+ let comb = new Comb({ 'always-semicolon': true });
+ let input = 'a { color: tomato }';
+ let expected = 'a { color: tomato; }';
+ return comb.processString(input).then(function(actual) {
+ assert.equal(actual, expected);
+ });
+ });
+
+ it('new Comb() should be chainable', function() {
+ let input = 'a { color: tomato; top: 0; }';
+ let expected = 'a {top: 0; color: tomato; }';
+ return (new Comb('zen')).processString(input).then(output => {
+ assert.equal(expected, output);
+ });
+ });
+
+ it('configure() should be chainable', function() {
+ let input = 'a { color: tomato }';
+ let expected = 'a { color: tomato; }';
+ return (new Comb()).configure({ 'always-semicolon': true })
+ .processString(input)
+ .then(actual => {
+ assert.equal(actual, expected);
+ });
+ });
+});
diff --git a/test/core/core_test.js b/test/core/core_test.js
new file mode 100644
index 00000000..4384753d
--- /dev/null
+++ b/test/core/core_test.js
@@ -0,0 +1,54 @@
+let assert = require('assert');
+let fs = require('fs');
+let path = require('path');
+
+let Comb = require('../../lib/csscomb');
+
+class CoreTest {
+ constructor(context, config) {
+ this.file = context.test.file;
+ this.syntax = context.test.parent.title;
+
+ this.Comb = Comb;
+ this.comb = new Comb();
+ if (config) this.comb.configure(config);
+ }
+
+ useConfig(name) {
+ let config = Comb.getConfig(name);
+ this.comb.configure(config);
+ }
+
+ getErrors(filename) {
+ let input = this.readFile(filename);
+ return this.comb.lintString(input, {syntax: this.syntax});
+ }
+
+ shouldBeEqual(inputFile, expectedFile) {
+ let input = this.readFile(inputFile);
+ let expected = expectedFile ? this.readFile(expectedFile) : input;
+
+ return this.comb.processString(input, {syntax: this.syntax})
+ .then(string => assert.equal(string, expected));
+ }
+
+ /**
+ * Detect options in a file and compare result with expected.
+ * File names should be relative to test suite's folder.
+ * @param {Array} options List of options that should be detected
+ * @param {String} input Name of template file
+ * @param {Object} expected Expected config with detected options
+ */
+ shouldDetect(options, input, expected) {
+ let detectedConfig = Comb.detectInString(input, options);
+ assert.deepEqual(detectedConfig, expected);
+ }
+
+ readFile(filename) {
+ let dirname = path.dirname(this.file);
+ let filePath = path.join(dirname, filename);
+ return fs.readFileSync(filePath, 'utf8');
+ }
+}
+
+module.exports = CoreTest;
diff --git a/test/core/get-config/test.js b/test/core/get-config/test.js
new file mode 100644
index 00000000..8db70a2f
--- /dev/null
+++ b/test/core/get-config/test.js
@@ -0,0 +1,63 @@
+var assert = require('assert');
+let Test = require('../core_test');
+
+describe('csscomb methods', function() {
+ it('getConfig()', function() {
+ let test = new Test(this);
+ var config = require('../../../config/csscomb.json');
+
+ assert.equal(test.Comb.getConfig(), config);
+ });
+
+ it('getConfig(number)', function() {
+ let test = new Test(this);
+
+ assert.throws(function() {
+ test.Comb.getConfig(16);
+ });
+ });
+
+ it('getConfig(boolean)', function() {
+ let test = new Test(this);
+
+ assert.throws(function() {
+ test.Comb.getConfig(true);
+ });
+ });
+
+ it('getConfig(empty string)', function() {
+ let test = new Test(this);
+ var config = require('../../../config/csscomb.json');
+
+ assert.equal(test.Comb.getConfig(''), config);
+ });
+
+ it('getConfig(invalid string)', function() {
+ let test = new Test(this);
+
+ assert.throws(function() {
+ test.Comb.getConfig('nani');
+ });
+ });
+
+ it('getConfig(csscomb)', function() {
+ let test = new Test(this);
+ var config = require('../../../config/csscomb.json');
+
+ assert.equal(test.Comb.getConfig('csscomb'), config);
+ });
+
+ it('getConfig(zen)', function() {
+ let test = new Test(this);
+ var config = require('../../../config/zen.json');
+
+ assert.equal(test.Comb.getConfig('zen'), config);
+ });
+
+ it('getConfig(yandex)', function() {
+ let test = new Test(this);
+ var config = require('../../../config/yandex.json');
+
+ assert.equal(test.Comb.getConfig('yandex'), config);
+ });
+});
diff --git a/test/core/less/import.less b/test/core/less/import.less
new file mode 100644
index 00000000..8faa1263
--- /dev/null
+++ b/test/core/less/import.less
@@ -0,0 +1,4 @@
+div {
+ @import "foo.css";
+ top: 0;
+}
diff --git a/test/core/less/interpolated-variable-1.less b/test/core/less/interpolated-variable-1.less
new file mode 100644
index 00000000..b0bc3148
--- /dev/null
+++ b/test/core/less/interpolated-variable-1.less
@@ -0,0 +1,4 @@
+div.@{nani} {
+ color:tomato;
+ top:0;
+}
diff --git a/test/core/less/interpolated-variable-2.less b/test/core/less/interpolated-variable-2.less
new file mode 100644
index 00000000..18a66ed2
--- /dev/null
+++ b/test/core/less/interpolated-variable-2.less
@@ -0,0 +1,4 @@
+div {
+ color: @{tomato};
+ top:0;
+}
diff --git a/test/core/less/mixin.less b/test/core/less/mixin.less
new file mode 100644
index 00000000..7e95951f
--- /dev/null
+++ b/test/core/less/mixin.less
@@ -0,0 +1,4 @@
+div {
+ .mixin;
+ top: 0;
+}
diff --git a/test/core/less/nested-media.less b/test/core/less/nested-media.less
new file mode 100644
index 00000000..346ddbf4
--- /dev/null
+++ b/test/core/less/nested-media.less
@@ -0,0 +1,6 @@
+div {
+ @media screen and (orientation: landscape) {
+ color: tomato;
+ }
+ top: 0;
+}
diff --git a/test/core/less/nested-rule.less b/test/core/less/nested-rule.less
new file mode 100644
index 00000000..5f74a2d9
--- /dev/null
+++ b/test/core/less/nested-rule.less
@@ -0,0 +1,6 @@
+div {
+ color: tomato;
+ a {
+ top: 0;
+ }
+}
diff --git a/test/core/less/operation.less b/test/core/less/operation.less
new file mode 100644
index 00000000..a3711aa3
--- /dev/null
+++ b/test/core/less/operation.less
@@ -0,0 +1,8 @@
+div {
+ @base: 5%;
+ @filler: @base * 2;
+ @other: @base + @filler;
+ color: #888 / 4;
+ background-color: @base-color + #111;
+ height: 100% / 2 + @filler;
+}
diff --git a/test/core/less/parent-selector.less b/test/core/less/parent-selector.less
new file mode 100644
index 00000000..61760301
--- /dev/null
+++ b/test/core/less/parent-selector.less
@@ -0,0 +1,8 @@
+div {
+ color: tomato;
+ &.top {
+ color: nani;
+ top: 0;
+ }
+ left: 0;
+}
diff --git a/test/core/less/test.js b/test/core/less/test.js
new file mode 100644
index 00000000..72a54eb4
--- /dev/null
+++ b/test/core/less/test.js
@@ -0,0 +1,66 @@
+let Test = require('../core_test');
+
+describe('less', function() {
+ it('Should parse nested rules', function() {
+ let test = new Test(this);
+ test.comb.configure({});
+
+ return test.shouldBeEqual('nested-rule.less');
+ });
+
+ it('Should parse operations', function() {
+ let test = new Test(this);
+ test.comb.configure({});
+
+ return test.shouldBeEqual('operation.less');
+ });
+
+ it('Should parse parent selector &', function() {
+ let test = new Test(this);
+ test.comb.configure({});
+
+ return test.shouldBeEqual('parent-selector.less');
+ });
+
+ it('Should parse variables', function() {
+ let test = new Test(this);
+ test.comb.configure({});
+
+ return test.shouldBeEqual('variable.less');
+ });
+
+ it('Should parse interpolated variables inside selectors', function() {
+ let test = new Test(this);
+ test.comb.configure({});
+
+ return test.shouldBeEqual('interpolated-variable-1.less');
+ });
+
+ it('Should parse interpolated variables inside values', function() {
+ let test = new Test(this);
+ test.comb.configure({});
+
+ return test.shouldBeEqual('interpolated-variable-2.less');
+ });
+
+ it('Should parse @import', function() {
+ let test = new Test(this);
+ test.comb.configure({});
+
+ return test.shouldBeEqual('import.less');
+ });
+
+ it('Should parse included mixins', function() {
+ let test = new Test(this);
+ test.comb.configure({});
+
+ return test.shouldBeEqual('mixin.less');
+ });
+
+ it('Should parse nested @media', function() {
+ let test = new Test(this);
+ test.comb.configure({});
+
+ return test.shouldBeEqual('nested-media.less');
+ });
+});
diff --git a/test/core/less/variable.less b/test/core/less/variable.less
new file mode 100644
index 00000000..8eba0cc3
--- /dev/null
+++ b/test/core/less/variable.less
@@ -0,0 +1,5 @@
+@red: tomato;
+div {
+ color: @tomato;
+ top: @@foo;
+}
diff --git a/test/core/scss/content.scss b/test/core/scss/content.scss
new file mode 100644
index 00000000..6852bccf
--- /dev/null
+++ b/test/core/scss/content.scss
@@ -0,0 +1,5 @@
+@mixin nani {
+ a {
+ @content;
+ }
+}
diff --git a/test/core/scss/default.scss b/test/core/scss/default.scss
new file mode 100644
index 00000000..12c10f9f
--- /dev/null
+++ b/test/core/scss/default.scss
@@ -0,0 +1,4 @@
+div {
+ color: tomato !default;
+ top: 0;
+}
diff --git a/test/core/scss/each.scss b/test/core/scss/each.scss
new file mode 100644
index 00000000..af596e68
--- /dev/null
+++ b/test/core/scss/each.scss
@@ -0,0 +1,7 @@
+div {
+ @each $animal in puma, sea-slug, erget {
+ .#{$animal}-icon {
+ background-image: url("/images/#{$animal}.png");
+ }
+ }
+}
diff --git a/test/core/scss/empty.scss b/test/core/scss/empty.scss
new file mode 100644
index 00000000..e69de29b
diff --git a/test/core/scss/extend-1.scss b/test/core/scss/extend-1.scss
new file mode 100644
index 00000000..c72afeac
--- /dev/null
+++ b/test/core/scss/extend-1.scss
@@ -0,0 +1,4 @@
+div {
+ @extend .nani;
+ top: 0;
+}
diff --git a/test/core/scss/extend-2.scss b/test/core/scss/extend-2.scss
new file mode 100644
index 00000000..c7bf3b58
--- /dev/null
+++ b/test/core/scss/extend-2.scss
@@ -0,0 +1,4 @@
+div {
+ @extend %nani;
+ top: 0;
+}
diff --git a/test/core/scss/for.scss b/test/core/scss/for.scss
new file mode 100644
index 00000000..64ff25aa
--- /dev/null
+++ b/test/core/scss/for.scss
@@ -0,0 +1,7 @@
+div {
+ @for $i from 1 through 3 {
+ .item-#{$i} {
+ width: 2em * 1;
+ }
+ }
+}
diff --git a/test/core/scss/function.scss b/test/core/scss/function.scss
new file mode 100644
index 00000000..8fcce438
--- /dev/null
+++ b/test/core/scss/function.scss
@@ -0,0 +1,3 @@
+@function nani($n) {
+ @return $n * 2;
+}
diff --git a/test/core/scss/if-else-if.scss b/test/core/scss/if-else-if.scss
new file mode 100644
index 00000000..f16e287d
--- /dev/null
+++ b/test/core/scss/if-else-if.scss
@@ -0,0 +1,7 @@
+div {
+ @if $type == ocean {
+ top: 0;
+ } @else if $type == monster {
+ left: 0;
+ }
+}
diff --git a/test/core/scss/if-else.scss b/test/core/scss/if-else.scss
new file mode 100644
index 00000000..236a6217
--- /dev/null
+++ b/test/core/scss/if-else.scss
@@ -0,0 +1,7 @@
+div {
+ @if $type == ocean {
+ top: 0;
+ } @else {
+ left: 0;
+ }
+}
diff --git a/test/core/scss/if.scss b/test/core/scss/if.scss
new file mode 100644
index 00000000..af9f4856
--- /dev/null
+++ b/test/core/scss/if.scss
@@ -0,0 +1,5 @@
+div {
+ @if $type == ocean {
+ top: 0;
+ }
+}
diff --git a/test/core/scss/import.scss b/test/core/scss/import.scss
new file mode 100644
index 00000000..8faa1263
--- /dev/null
+++ b/test/core/scss/import.scss
@@ -0,0 +1,4 @@
+div {
+ @import "foo.css";
+ top: 0;
+}
diff --git a/test/core/scss/include.scss b/test/core/scss/include.scss
new file mode 100644
index 00000000..2c918808
--- /dev/null
+++ b/test/core/scss/include.scss
@@ -0,0 +1,4 @@
+div {
+ @include nani($panda);
+ top: 0;
+}
diff --git a/test/core/scss/interpolated-variable-1.scss b/test/core/scss/interpolated-variable-1.scss
new file mode 100644
index 00000000..a6c28cf4
--- /dev/null
+++ b/test/core/scss/interpolated-variable-1.scss
@@ -0,0 +1,4 @@
+div.#{$nani} {
+ color: tomato;
+ top:0;
+}
diff --git a/test/core/scss/interpolated-variable-2.scss b/test/core/scss/interpolated-variable-2.scss
new file mode 100644
index 00000000..a19498f5
--- /dev/null
+++ b/test/core/scss/interpolated-variable-2.scss
@@ -0,0 +1,4 @@
+div {
+ color: #{$tomato};
+ top: 0;
+}
diff --git a/test/core/scss/mixin-1.scss b/test/core/scss/mixin-1.scss
new file mode 100644
index 00000000..32a77150
--- /dev/null
+++ b/test/core/scss/mixin-1.scss
@@ -0,0 +1,6 @@
+@mixin nani {
+ color: tomato;
+}
+.foo {
+ @include nani;
+}
diff --git a/test/core/scss/mixin-2.scss b/test/core/scss/mixin-2.scss
new file mode 100644
index 00000000..b779b6de
--- /dev/null
+++ b/test/core/scss/mixin-2.scss
@@ -0,0 +1,6 @@
+@mixin nani($tomato) {
+ color: $tomato;
+}
+.foo {
+ @include nani(red);
+}
diff --git a/test/core/scss/mixin-3.scss b/test/core/scss/mixin-3.scss
new file mode 100644
index 00000000..2c19b358
--- /dev/null
+++ b/test/core/scss/mixin-3.scss
@@ -0,0 +1,6 @@
+@mixin nani($shadows...) {
+ box-shadow: $shadows;
+}
+.foo {
+ @include nani(0px 4px 5px #666, 2px 6px 10px #999);
+}
diff --git a/test/core/scss/mixin-4.scss b/test/core/scss/mixin-4.scss
new file mode 100644
index 00000000..4e46ffed
--- /dev/null
+++ b/test/core/scss/mixin-4.scss
@@ -0,0 +1,6 @@
+.foo {
+ @include nani {
+ color: tomato;
+ top: 0
+ }
+}
diff --git a/test/core/scss/nested-media.scss b/test/core/scss/nested-media.scss
new file mode 100644
index 00000000..346ddbf4
--- /dev/null
+++ b/test/core/scss/nested-media.scss
@@ -0,0 +1,6 @@
+div {
+ @media screen and (orientation: landscape) {
+ color: tomato;
+ }
+ top: 0;
+}
diff --git a/test/core/scss/nested-property.scss b/test/core/scss/nested-property.scss
new file mode 100644
index 00000000..2f6384a3
--- /dev/null
+++ b/test/core/scss/nested-property.scss
@@ -0,0 +1,8 @@
+div {
+ color: tomato;
+ font: 2px/3px {
+ family: fantasy;
+ size: 30em;
+ }
+ left: 0;
+}
diff --git a/test/core/scss/nested-rule.scss b/test/core/scss/nested-rule.scss
new file mode 100644
index 00000000..5f74a2d9
--- /dev/null
+++ b/test/core/scss/nested-rule.scss
@@ -0,0 +1,6 @@
+div {
+ color: tomato;
+ a {
+ top: 0;
+ }
+}
diff --git a/test/core/scss/parent-selector.scss b/test/core/scss/parent-selector.scss
new file mode 100644
index 00000000..61760301
--- /dev/null
+++ b/test/core/scss/parent-selector.scss
@@ -0,0 +1,8 @@
+div {
+ color: tomato;
+ &.top {
+ color: nani;
+ top: 0;
+ }
+ left: 0;
+}
diff --git a/test/core/scss/test.js b/test/core/scss/test.js
new file mode 100644
index 00000000..06c50d67
--- /dev/null
+++ b/test/core/scss/test.js
@@ -0,0 +1,186 @@
+let Test = require('../core_test');
+
+describe('scss', function() {
+ it('Should parse nested rules', function() {
+ let test = new Test(this);
+ test.comb.configure({});
+
+ return test.shouldBeEqual('nested-rule.scss');
+ });
+
+ it('Should parse parent selector &', function() {
+ let test = new Test(this);
+ test.comb.configure({});
+
+ return test.shouldBeEqual('parent-selector.scss');
+ });
+
+ it('Should parse nested properties', function() {
+ let test = new Test(this);
+ test.comb.configure({});
+
+ return test.shouldBeEqual('nested-property.scss');
+ });
+
+ it('Should parse variables', function() {
+ let test = new Test(this);
+ test.comb.configure({});
+
+ return test.shouldBeEqual('variable.scss');
+ });
+
+ it('Should parse interpolated variables inside selectors', function() {
+ let test = new Test(this);
+ test.comb.configure({});
+
+ return test.shouldBeEqual('interpolated-variable-1.scss');
+ });
+
+ it('Should parse interpolated variables inside values', function() {
+ let test = new Test(this);
+ test.comb.configure({});
+
+ return test.shouldBeEqual('interpolated-variable-2.scss');
+ });
+
+ it('Should parse defaults', function() {
+ let test = new Test(this);
+ test.comb.configure({});
+
+ return test.shouldBeEqual('default.scss');
+ });
+
+ it('Should parse @import', function() {
+ let test = new Test(this);
+ test.comb.configure({});
+
+ return test.shouldBeEqual('import.scss');
+ });
+
+ it('Should parse @include', function() {
+ let test = new Test(this);
+ test.comb.configure({});
+
+ return test.shouldBeEqual('include.scss');
+ });
+
+ it('Should parse nested @media', function() {
+ let test = new Test(this);
+ test.comb.configure({});
+
+ return test.shouldBeEqual('nested-media.scss');
+ });
+
+ it('Should parse @extend with classes', function() {
+ let test = new Test(this);
+ test.comb.configure({});
+
+ return test.shouldBeEqual('extend-1.scss');
+ });
+
+ it('Should parse @extend with placeholders', function() {
+ let test = new Test(this);
+ test.comb.configure({});
+
+ return test.shouldBeEqual('extend-2.scss');
+ });
+
+ it('Should parse @warn', function() {
+ let test = new Test(this);
+ test.comb.configure({});
+
+ return test.shouldBeEqual('warn.scss');
+ });
+
+ it('Should parse @if', function() {
+ let test = new Test(this);
+ test.comb.configure({});
+
+ return test.shouldBeEqual('if.scss');
+ });
+
+ it('Should parse @if and @else', function() {
+ let test = new Test(this);
+ test.comb.configure({});
+
+ return test.shouldBeEqual('if-else.scss');
+ });
+
+ it('Should parse @if and @else if', function() {
+ let test = new Test(this);
+ test.comb.configure({});
+
+ return test.shouldBeEqual('if-else-if.scss');
+ });
+
+ it('Should parse @for', function() {
+ let test = new Test(this);
+ test.comb.configure({});
+
+ return test.shouldBeEqual('for.scss');
+ });
+
+ it('Should parse @each', function() {
+ let test = new Test(this);
+ test.comb.configure({});
+
+ return test.shouldBeEqual('each.scss');
+ });
+
+ it('Should parse @while', function() {
+ let test = new Test(this);
+ test.comb.configure({});
+
+ return test.shouldBeEqual('while.scss');
+ });
+
+ it('Should parse mixins', function() {
+ let test = new Test(this);
+ test.comb.configure({});
+
+ return test.shouldBeEqual('mixin-1.scss');
+ });
+
+ it('Should parse passing several variables to a mixin', function() {
+ let test = new Test(this);
+ test.comb.configure({});
+
+ return test.shouldBeEqual('mixin-2.scss');
+ });
+
+ it('Should parse passing a list of variables to a mixin', function() {
+ let test = new Test(this);
+ test.comb.configure({});
+
+ return test.shouldBeEqual('mixin-3.scss');
+ });
+
+ it('Should parse passing a content block to a mixin', function() {
+ let test = new Test(this);
+ test.comb.configure({});
+
+ return test.shouldBeEqual('mixin-4.scss');
+ });
+
+ it('Should parse @content', function() {
+ let test = new Test(this);
+ test.comb.configure({});
+
+ return test.shouldBeEqual('content.scss');
+ });
+
+ it('Should parse an empty file', function() {
+ let test = new Test(this);
+ test.comb.configure({});
+
+ return test.shouldBeEqual('empty.scss');
+ });
+
+ it('Should parse functions', function() {
+ let test = new Test(this);
+ test.comb.configure({});
+
+ return test.shouldBeEqual('function.scss');
+ });
+});
+
diff --git a/test/core/scss/variable.scss b/test/core/scss/variable.scss
new file mode 100644
index 00000000..d553b927
--- /dev/null
+++ b/test/core/scss/variable.scss
@@ -0,0 +1,4 @@
+$red: tomato;
+div {
+ color: $tomato;
+}
diff --git a/test/core/scss/warn.scss b/test/core/scss/warn.scss
new file mode 100644
index 00000000..c7961a18
--- /dev/null
+++ b/test/core/scss/warn.scss
@@ -0,0 +1,4 @@
+div {
+ @warn "nani";
+ top: 0;
+}
diff --git a/test/core/scss/while.scss b/test/core/scss/while.scss
new file mode 100644
index 00000000..89c6cfa5
--- /dev/null
+++ b/test/core/scss/while.scss
@@ -0,0 +1,8 @@
+div {
+ @while $i > 6 {
+ .item {
+ width: 2em * $i;
+ }
+ $i: $i - 2;
+ }
+}
diff --git a/test/core/use/test.js b/test/core/use/test.js
new file mode 100644
index 00000000..1d3a9aa7
--- /dev/null
+++ b/test/core/use/test.js
@@ -0,0 +1,42 @@
+var assert = require('assert');
+let Test = require('../core_test');
+
+describe('.use()', function() {
+ it('Should set predefined options in correct order', function() {
+ let test = new Test(this);
+ var config = test.Comb.getConfig('csscomb');
+ test.comb.configure(config);
+ var options = test.comb.plugins.map(function(plugin) {
+ return plugin.name;
+ });
+ var expected = [
+ 'always-semicolon',
+ 'lines-between-rulesets',
+ 'remove-empty-rulesets',
+ 'color-case',
+ 'color-shorthand',
+ 'element-case',
+ 'eof-newline',
+ 'leading-zero',
+ 'quotes',
+ 'sort-order-fallback',
+ 'space-after-colon',
+ 'space-after-combinator',
+ 'space-after-opening-brace',
+ 'space-after-selector-delimiter',
+ 'space-before-colon',
+ 'space-before-combinator',
+ 'space-before-opening-brace',
+ 'space-before-selector-delimiter',
+ 'space-between-declarations',
+ 'block-indent',
+ 'sort-order',
+ 'strip-spaces',
+ 'space-before-closing-brace',
+ 'unitless-zero',
+ 'tab-size',
+ 'vendor-prefix-align'
+ ];
+ assert.deepEqual(options, expected);
+ });
+});
diff --git a/test/element-case.js b/test/element-case.js
deleted file mode 100644
index 27fac093..00000000
--- a/test/element-case.js
+++ /dev/null
@@ -1,51 +0,0 @@
-var Comb = require('../lib/csscomb');
-var assert = require('assert');
-
-describe('options/element-case', function() {
- var comb;
-
- beforeEach(function() {
- comb = new Comb();
- });
-
- it('Invalid String should not change case of elements', function() {
- comb.configure({ 'element-case': 'foobar' });
- assert.equal(
- comb.processString(
- 'LI a { color : red }'
- ),
- 'LI a { color : red }'
- );
- });
-
- it('Should switch tag name to upper case', function() {
- comb.configure({ 'element-case': 'upper' });
- assert.equal(
- comb.processString(
- 'div { color: #fff }'
- ),
- 'DIV { color: #fff }'
- );
- });
-
- it('Should switch tag name to lower case', function() {
- comb.configure({ 'element-case': 'lower' });
- assert.equal(
- comb.processString(
- 'DIV { color: #FFF }'
- ),
- 'div { color: #FFF }'
- );
- });
-
- it('Should switch element-case in complex rules', function() {
- comb.configure({ 'element-case': 'lower' });
- assert.equal(
- comb.processString(
- 'UL > LI > .foo:not(A) { color: red }'
- ),
- 'ul > li > .foo:not(a) { color: red }'
- );
- });
-
-});
diff --git a/test/eof-newline.js b/test/eof-newline.js
deleted file mode 100644
index 9ed5f3f6..00000000
--- a/test/eof-newline.js
+++ /dev/null
@@ -1,34 +0,0 @@
-var Comb = require('../lib/csscomb');
-var assert = require('assert');
-
-describe('options/eof-newline', function() {
- var comb;
- beforeEach(function() {
- comb = new Comb();
- });
- it('Invalid value should not change trim trailing brac', function() {
- comb.configure({ 'eof-newline': 'foobar' });
- assert.equal(
- comb.processString('a { color: red } \n'),
- 'a { color: red } \n'
- );
- });
- it('Boolean true value should insert line-break at eof', function() {
- comb.configure({ 'eof-newline': true });
- assert.equal(
- comb.processString(
- 'a {color:red} '
- ),
- 'a {color:red} \n'
- );
- });
- it('Boolean false value should remove line-break from eof', function() {
- comb.configure({ 'eof-newline': false });
- assert.equal(
- comb.processString(
- 'a {color:red} \n'
- ),
- 'a {color:red} '
- );
- });
-});
diff --git a/test/integral.js b/test/integral.js
deleted file mode 100644
index bd79b60c..00000000
--- a/test/integral.js
+++ /dev/null
@@ -1,26 +0,0 @@
-var Comb = require('../lib/csscomb');
-var assert = require('assert');
-var vow = require('vow');
-var vfs = require('vow-fs');
-
-describe('integral test', function() {
- var comb;
- it('Process result must be equal to expect.css', function(done) {
- comb = new Comb();
- comb.configure(require('../.csscomb.json'));
- vow.all(['origin', 'expect'].map(function(type) {
- var fileName = './test/integral.' + type + '.css';
- return vfs.read(fileName, 'utf8').then(function(data) {
- return data;
- });
- }))
- .then(function(results) {
- try {
- assert.equal(comb.processString(results[0]), results[1]);
- done();
- } catch (e) {
- done(e);
- }
- });
- });
-});
diff --git a/test/leading-zero.js b/test/leading-zero.js
deleted file mode 100644
index 96c6ff03..00000000
--- a/test/leading-zero.js
+++ /dev/null
@@ -1,31 +0,0 @@
-var Comb = require('../lib/csscomb');
-var assert = require('assert');
-
-describe('options/leading-zero', function() {
- var comb;
-
- beforeEach(function() {
- comb = new Comb();
- });
-
- it('Should add leading zero in dimensions', function() {
- comb.configure({ 'leading-zero': true });
- assert.equal(
- comb.processString(
- 'div { margin: .5em }'
- ),
- 'div { margin: 0.5em }'
- );
- });
-
- it('Should remove leading zero in dimensions', function() {
- comb.configure({ 'leading-zero': false });
- assert.equal(
- comb.processString(
- 'div { margin: 0.5em }'
- ),
- 'div { margin: .5em }'
- );
- });
-
-});
diff --git a/test/mocha.js b/test/mocha.js
new file mode 100644
index 00000000..98e0ee7b
--- /dev/null
+++ b/test/mocha.js
@@ -0,0 +1,17 @@
+let glob = require('glob');
+let Mocha = require('mocha');
+
+let mocha = new Mocha();
+if (process.env.TEST_COV) mocha.reporter('html-cov');
+
+// Tell mocha which tests to run:
+glob.sync('test/**/test.js').forEach(file => {
+ mocha.addFile(file);
+});
+
+mocha.run(failures => {
+ process.on('exit', () => {
+ process.exit(failures);
+ });
+});
+
diff --git a/test/options/always-semicolon/detect/css/test-1.css b/test/options/always-semicolon/detect/css/test-1.css
new file mode 100644
index 00000000..e69de29b
diff --git a/test/options/always-semicolon/detect/css/test-1.expected.css b/test/options/always-semicolon/detect/css/test-1.expected.css
new file mode 100644
index 00000000..e69de29b
diff --git a/test/options/always-semicolon/detect/css/test-2.css b/test/options/always-semicolon/detect/css/test-2.css
new file mode 100644
index 00000000..e69de29b
diff --git a/test/options/always-semicolon/detect/css/test-2.expected.css b/test/options/always-semicolon/detect/css/test-2.expected.css
new file mode 100644
index 00000000..e69de29b
diff --git a/test/options/always-semicolon/detect/css/test-3.css b/test/options/always-semicolon/detect/css/test-3.css
new file mode 100644
index 00000000..e69de29b
diff --git a/test/options/always-semicolon/detect/css/test-3.expected.css b/test/options/always-semicolon/detect/css/test-3.expected.css
new file mode 100644
index 00000000..e69de29b
diff --git a/test/options/always-semicolon/detect/css/test-4.css b/test/options/always-semicolon/detect/css/test-4.css
new file mode 100644
index 00000000..e69de29b
diff --git a/test/options/always-semicolon/detect/css/test-4.expected.css b/test/options/always-semicolon/detect/css/test-4.expected.css
new file mode 100644
index 00000000..e69de29b
diff --git a/test/options/always-semicolon/detect/css/test-5.css b/test/options/always-semicolon/detect/css/test-5.css
new file mode 100644
index 00000000..e69de29b
diff --git a/test/options/always-semicolon/detect/css/test-5.expected.css b/test/options/always-semicolon/detect/css/test-5.expected.css
new file mode 100644
index 00000000..e69de29b
diff --git a/test/options/always-semicolon/detect/css/test-6.css b/test/options/always-semicolon/detect/css/test-6.css
new file mode 100644
index 00000000..e69de29b
diff --git a/test/options/always-semicolon/detect/css/test-6.expected.css b/test/options/always-semicolon/detect/css/test-6.expected.css
new file mode 100644
index 00000000..e69de29b
diff --git a/test/options/always-semicolon/detect/css/test-7.css b/test/options/always-semicolon/detect/css/test-7.css
new file mode 100644
index 00000000..e69de29b
diff --git a/test/options/always-semicolon/detect/css/test-7.expected.css b/test/options/always-semicolon/detect/css/test-7.expected.css
new file mode 100644
index 00000000..e69de29b
diff --git a/test/options/always-semicolon/detect/test.js b/test/options/always-semicolon/detect/test.js
new file mode 100644
index 00000000..6d22001a
--- /dev/null
+++ b/test/options/always-semicolon/detect/test.js
@@ -0,0 +1,72 @@
+let Test = require('../../option_test');
+
+describe('Option `always-semicolon`, detect', function() {
+ describe('css', function() {
+ it('Should detect semicolon for last property. Test 1', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['always-semicolon'],
+ 'div { height: 0 }',
+ {'always-semicolon': false}
+ );
+ });
+
+ it('Should detect semicolon for last property. Test 2', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['always-semicolon'],
+ 'div { height: 0; }',
+ {'always-semicolon': true}
+ );
+ });
+
+ it('Should detect semicolon for last property. Test 3', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['always-semicolon'],
+ 'div { height: 0; } div { height: 0 }',
+ {'always-semicolon': true}
+ );
+ });
+
+ it('Should detect semicolon for last property. Test 4', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['always-semicolon'],
+ 'div { height: 0 } div { height: 0; } div { height: 0 }',
+ {'always-semicolon': false}
+ );
+ });
+
+ it('Should detect semicolon for last property. Test 5', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['always-semicolon'],
+ 'div {\nheight: 0 /* Comment */\n} ' +
+ 'div { height: 0; }' +
+ 'div {\ntop: 1px;\nheight: 0 /* 1comment */ /* 2comment */\n}',
+ {'always-semicolon': false}
+ );
+ });
+
+
+ it('Should detect semicolon for last property. Test 6', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['always-semicolon'],
+ 'a{\n border:0;\n}',
+ {'always-semicolon': true}
+ );
+ });
+
+ it('Should not detect semicolon for last property if there are no properties', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['always-semicolon'],
+ 'div {}',
+ {}
+ );
+ });
+ });
+});
+
diff --git a/test/options/always-semicolon/lint/css/lint-1.css b/test/options/always-semicolon/lint/css/lint-1.css
new file mode 100644
index 00000000..0dbee0fe
--- /dev/null
+++ b/test/options/always-semicolon/lint/css/lint-1.css
@@ -0,0 +1 @@
+div { height: 0; }
diff --git a/test/options/always-semicolon/lint/css/lint-2.css b/test/options/always-semicolon/lint/css/lint-2.css
new file mode 100644
index 00000000..f36f0139
--- /dev/null
+++ b/test/options/always-semicolon/lint/css/lint-2.css
@@ -0,0 +1 @@
+div { height: 0 }
diff --git a/test/options/always-semicolon/lint/css/lint-3.css b/test/options/always-semicolon/lint/css/lint-3.css
new file mode 100644
index 00000000..e47a2a9c
--- /dev/null
+++ b/test/options/always-semicolon/lint/css/lint-3.css
@@ -0,0 +1,9 @@
+div {
+ height: 0
+}
+
+@media screen {
+ .a {
+ color: tomato;
+ position: panda}
+}
diff --git a/test/options/always-semicolon/lint/test.js b/test/options/always-semicolon/lint/test.js
new file mode 100644
index 00000000..80cb27de
--- /dev/null
+++ b/test/options/always-semicolon/lint/test.js
@@ -0,0 +1,44 @@
+let assert = require('assert');
+let Test = require('../../option_test');
+
+describe('Option `always-semicolon`, lint', function() {
+ describe('css', function() {
+ it('Should report no errors', function() {
+ let test = new Test(this, {'always-semicolon': true});
+ return test.getErrors('lint-1.css').then(errors => {
+ assert.equal(errors.length, 0);
+ });
+ });
+
+ it('Error mesage should be a string', function() {
+ let test = new Test(this, {'always-semicolon': true});
+ return test.getErrors('lint-2.css').then(errors => {
+ let error = errors[0];
+ assert.equal(typeof error.message, 'string');
+ });
+ });
+
+ it('Error should provide correct position info', function() {
+ let test = new Test(this, {'always-semicolon': true});
+ return test.getErrors('lint-2.css').then(errors => {
+ let error = errors[0];
+ assert.equal(error.line, 1);
+ assert.equal(error.column, 16);
+ });
+ });
+
+ it('Should report multiple errors', function() {
+ let test = new Test(this, {'always-semicolon': true});
+ return test.getErrors('lint-3.css').then(errors => {
+ assert.equal(errors.length, 2);
+
+ assert.equal(errors[0].line, 2);
+ assert.equal(errors[0].column, 14);
+
+ assert.equal(errors[1].line, 8);
+ assert.equal(errors[1].column, 24);
+ });
+ });
+ });
+});
+
diff --git a/test/options/always-semicolon/process/css/test-1.css b/test/options/always-semicolon/process/css/test-1.css
new file mode 100644
index 00000000..f36f0139
--- /dev/null
+++ b/test/options/always-semicolon/process/css/test-1.css
@@ -0,0 +1 @@
+div { height: 0 }
diff --git a/test/options/always-semicolon/process/css/test-1.expected.css b/test/options/always-semicolon/process/css/test-1.expected.css
new file mode 100644
index 00000000..0dbee0fe
--- /dev/null
+++ b/test/options/always-semicolon/process/css/test-1.expected.css
@@ -0,0 +1 @@
+div { height: 0; }
diff --git a/test/options/always-semicolon/process/css/test-2.css b/test/options/always-semicolon/process/css/test-2.css
new file mode 100644
index 00000000..c1888d9f
--- /dev/null
+++ b/test/options/always-semicolon/process/css/test-2.css
@@ -0,0 +1,3 @@
+div {
+height: 0
+}
diff --git a/test/options/always-semicolon/process/css/test-2.expected.css b/test/options/always-semicolon/process/css/test-2.expected.css
new file mode 100644
index 00000000..d5a75f33
--- /dev/null
+++ b/test/options/always-semicolon/process/css/test-2.expected.css
@@ -0,0 +1,3 @@
+div {
+height: 0;
+}
diff --git a/test/options/always-semicolon/process/css/test-3.css b/test/options/always-semicolon/process/css/test-3.css
new file mode 100644
index 00000000..8840e0e0
--- /dev/null
+++ b/test/options/always-semicolon/process/css/test-3.css
@@ -0,0 +1 @@
+div {height: 0}
diff --git a/test/options/always-semicolon/process/css/test-3.expected.css b/test/options/always-semicolon/process/css/test-3.expected.css
new file mode 100644
index 00000000..8fe52809
--- /dev/null
+++ b/test/options/always-semicolon/process/css/test-3.expected.css
@@ -0,0 +1 @@
+div {height: 0;}
diff --git a/test/options/always-semicolon/process/css/test-4.css b/test/options/always-semicolon/process/css/test-4.css
new file mode 100644
index 00000000..d72a31ce
--- /dev/null
+++ b/test/options/always-semicolon/process/css/test-4.css
@@ -0,0 +1,3 @@
+div {
+height: 0 /* Comment */
+}
diff --git a/test/options/always-semicolon/process/css/test-4.expected.css b/test/options/always-semicolon/process/css/test-4.expected.css
new file mode 100644
index 00000000..4710445d
--- /dev/null
+++ b/test/options/always-semicolon/process/css/test-4.expected.css
@@ -0,0 +1,3 @@
+div {
+height: 0; /* Comment */
+}
diff --git a/test/options/always-semicolon/process/css/test-5.css b/test/options/always-semicolon/process/css/test-5.css
new file mode 100644
index 00000000..578fd29c
--- /dev/null
+++ b/test/options/always-semicolon/process/css/test-5.css
@@ -0,0 +1,4 @@
+div {
+top: 1px;
+height: 0 /* 1comment */ /* 2comment */
+}
diff --git a/test/options/always-semicolon/process/css/test-5.expected.css b/test/options/always-semicolon/process/css/test-5.expected.css
new file mode 100644
index 00000000..1869f051
--- /dev/null
+++ b/test/options/always-semicolon/process/css/test-5.expected.css
@@ -0,0 +1,4 @@
+div {
+top: 1px;
+height: 0; /* 1comment */ /* 2comment */
+}
diff --git a/test/options/always-semicolon/process/less/condition-multiline.less b/test/options/always-semicolon/process/less/condition-multiline.less
new file mode 100644
index 00000000..e4213d2f
--- /dev/null
+++ b/test/options/always-semicolon/process/less/condition-multiline.less
@@ -0,0 +1,6 @@
+div {
+ @color: tomato;
+ div when (@color = tomato) {
+ top: 0;
+ }
+ }
diff --git a/test/options/always-semicolon/process/less/condition.less b/test/options/always-semicolon/process/less/condition.less
new file mode 100644
index 00000000..5608abdc
--- /dev/null
+++ b/test/options/always-semicolon/process/less/condition.less
@@ -0,0 +1 @@
+div { @color: tomato; div when (@color = tomato) { top: 0; } }
diff --git a/test/options/always-semicolon/process/less/include-1-multiline.expected.less b/test/options/always-semicolon/process/less/include-1-multiline.expected.less
new file mode 100644
index 00000000..77cf6660
--- /dev/null
+++ b/test/options/always-semicolon/process/less/include-1-multiline.expected.less
@@ -0,0 +1,4 @@
+div {
+ color: tomato;
+ .nani;
+ }
diff --git a/test/options/always-semicolon/process/less/include-1-multiline.less b/test/options/always-semicolon/process/less/include-1-multiline.less
new file mode 100644
index 00000000..9daab170
--- /dev/null
+++ b/test/options/always-semicolon/process/less/include-1-multiline.less
@@ -0,0 +1,4 @@
+div {
+ color: tomato;
+ .nani
+ }
diff --git a/test/options/always-semicolon/process/less/include-1.expected.less b/test/options/always-semicolon/process/less/include-1.expected.less
new file mode 100644
index 00000000..9058edc9
--- /dev/null
+++ b/test/options/always-semicolon/process/less/include-1.expected.less
@@ -0,0 +1 @@
+div { color: tomato; .nani; }
diff --git a/test/options/always-semicolon/process/less/include-1.less b/test/options/always-semicolon/process/less/include-1.less
new file mode 100644
index 00000000..4e470177
--- /dev/null
+++ b/test/options/always-semicolon/process/less/include-1.less
@@ -0,0 +1 @@
+div { color: tomato; .nani }
diff --git a/test/options/always-semicolon/process/less/include-2-multiline.expected.less b/test/options/always-semicolon/process/less/include-2-multiline.expected.less
new file mode 100644
index 00000000..6877e14b
--- /dev/null
+++ b/test/options/always-semicolon/process/less/include-2-multiline.expected.less
@@ -0,0 +1,4 @@
+div {
+ color: tomato;
+ .nani(2px);
+ }
diff --git a/test/options/always-semicolon/process/less/include-2-multiline.less b/test/options/always-semicolon/process/less/include-2-multiline.less
new file mode 100644
index 00000000..c419ae5f
--- /dev/null
+++ b/test/options/always-semicolon/process/less/include-2-multiline.less
@@ -0,0 +1,4 @@
+div {
+ color: tomato;
+ .nani(2px)
+ }
diff --git a/test/options/always-semicolon/process/less/include-2.expected.less b/test/options/always-semicolon/process/less/include-2.expected.less
new file mode 100644
index 00000000..3fb43359
--- /dev/null
+++ b/test/options/always-semicolon/process/less/include-2.expected.less
@@ -0,0 +1 @@
+div { color: tomato; .nani(2px); }
diff --git a/test/options/always-semicolon/process/less/include-2.less b/test/options/always-semicolon/process/less/include-2.less
new file mode 100644
index 00000000..1ed55fed
--- /dev/null
+++ b/test/options/always-semicolon/process/less/include-2.less
@@ -0,0 +1 @@
+div { color: tomato; .nani(2px) }
diff --git a/test/options/always-semicolon/process/less/include-3-multiline.expected.less b/test/options/always-semicolon/process/less/include-3-multiline.expected.less
new file mode 100644
index 00000000..d51df782
--- /dev/null
+++ b/test/options/always-semicolon/process/less/include-3-multiline.expected.less
@@ -0,0 +1,4 @@
+div {
+ color: tomato;
+ .nani(2px) !important;
+ }
diff --git a/test/options/always-semicolon/process/less/include-3-multiline.less b/test/options/always-semicolon/process/less/include-3-multiline.less
new file mode 100644
index 00000000..4d628b86
--- /dev/null
+++ b/test/options/always-semicolon/process/less/include-3-multiline.less
@@ -0,0 +1,4 @@
+div {
+ color: tomato;
+ .nani(2px) !important
+ }
diff --git a/test/options/always-semicolon/process/less/include-3.expected.less b/test/options/always-semicolon/process/less/include-3.expected.less
new file mode 100644
index 00000000..117678f1
--- /dev/null
+++ b/test/options/always-semicolon/process/less/include-3.expected.less
@@ -0,0 +1 @@
+div { color: tomato; .nani(2px) !important; }
diff --git a/test/options/always-semicolon/process/less/include-3.less b/test/options/always-semicolon/process/less/include-3.less
new file mode 100644
index 00000000..30988983
--- /dev/null
+++ b/test/options/always-semicolon/process/less/include-3.less
@@ -0,0 +1 @@
+div { color: tomato; .nani(2px) !important }
diff --git a/test/options/always-semicolon/process/less/include-4-multiline.expected.less b/test/options/always-semicolon/process/less/include-4-multiline.expected.less
new file mode 100644
index 00000000..1efba9ef
--- /dev/null
+++ b/test/options/always-semicolon/process/less/include-4-multiline.expected.less
@@ -0,0 +1,4 @@
+div {
+ color: tomato;
+ #bundle > .button;
+ }
diff --git a/test/options/always-semicolon/process/less/include-4-multiline.less b/test/options/always-semicolon/process/less/include-4-multiline.less
new file mode 100644
index 00000000..f16812dc
--- /dev/null
+++ b/test/options/always-semicolon/process/less/include-4-multiline.less
@@ -0,0 +1,4 @@
+div {
+ color: tomato;
+ #bundle > .button
+ }
diff --git a/test/options/always-semicolon/process/less/include-4.expected.less b/test/options/always-semicolon/process/less/include-4.expected.less
new file mode 100644
index 00000000..1b3c6d88
--- /dev/null
+++ b/test/options/always-semicolon/process/less/include-4.expected.less
@@ -0,0 +1 @@
+div { color: tomato; #bundle > .button; }
diff --git a/test/options/always-semicolon/process/less/include-4.less b/test/options/always-semicolon/process/less/include-4.less
new file mode 100644
index 00000000..9221814e
--- /dev/null
+++ b/test/options/always-semicolon/process/less/include-4.less
@@ -0,0 +1 @@
+div { color: tomato; #bundle > .button }
diff --git a/test/options/always-semicolon/process/less/include-5-multiline.expected.less b/test/options/always-semicolon/process/less/include-5-multiline.expected.less
new file mode 100644
index 00000000..4c15e42d
--- /dev/null
+++ b/test/options/always-semicolon/process/less/include-5-multiline.expected.less
@@ -0,0 +1,4 @@
+div {
+ @color: tomato;
+ #bundle > .button (@color);
+ }
diff --git a/test/options/always-semicolon/process/less/include-5-multiline.less b/test/options/always-semicolon/process/less/include-5-multiline.less
new file mode 100644
index 00000000..15d03fca
--- /dev/null
+++ b/test/options/always-semicolon/process/less/include-5-multiline.less
@@ -0,0 +1,4 @@
+div {
+ @color: tomato;
+ #bundle > .button (@color)
+ }
diff --git a/test/options/always-semicolon/process/less/include-5.expected.less b/test/options/always-semicolon/process/less/include-5.expected.less
new file mode 100644
index 00000000..718c5c53
--- /dev/null
+++ b/test/options/always-semicolon/process/less/include-5.expected.less
@@ -0,0 +1 @@
+div { @color: tomato; #bundle > .button (@color); }
diff --git a/test/options/always-semicolon/process/less/include-5.less b/test/options/always-semicolon/process/less/include-5.less
new file mode 100644
index 00000000..6bf447cd
--- /dev/null
+++ b/test/options/always-semicolon/process/less/include-5.less
@@ -0,0 +1 @@
+div { @color: tomato; #bundle > .button (@color) }
diff --git a/test/options/always-semicolon/process/sass/test.sass b/test/options/always-semicolon/process/sass/test.sass
new file mode 100644
index 00000000..6284097f
--- /dev/null
+++ b/test/options/always-semicolon/process/sass/test.sass
@@ -0,0 +1,7 @@
+a
+ color: tomato
+ top: 0
+
+ p
+ a: b
+ c:d
diff --git a/test/options/always-semicolon/process/scss/block-include-multiline.scss b/test/options/always-semicolon/process/scss/block-include-multiline.scss
new file mode 100644
index 00000000..a9e5aa98
--- /dev/null
+++ b/test/options/always-semicolon/process/scss/block-include-multiline.scss
@@ -0,0 +1,7 @@
+div {
+ color: tomato;
+ @include nani {
+ top: 0;
+ content: '';
+ }
+ }
diff --git a/test/options/always-semicolon/process/scss/block-include.scss b/test/options/always-semicolon/process/scss/block-include.scss
new file mode 100644
index 00000000..251fa784
--- /dev/null
+++ b/test/options/always-semicolon/process/scss/block-include.scss
@@ -0,0 +1 @@
+div { color: tomato; @include nani { top: 0; content: ''; } }
diff --git a/test/options/always-semicolon/process/scss/block-value-multiline.scss b/test/options/always-semicolon/process/scss/block-value-multiline.scss
new file mode 100644
index 00000000..f654043d
--- /dev/null
+++ b/test/options/always-semicolon/process/scss/block-value-multiline.scss
@@ -0,0 +1,7 @@
+div {
+ color: tomato;
+ font: {
+ family: fantasy;
+ size: 16px;
+ }
+ }
diff --git a/test/options/always-semicolon/process/scss/block-value.scss b/test/options/always-semicolon/process/scss/block-value.scss
new file mode 100644
index 00000000..15f19e27
--- /dev/null
+++ b/test/options/always-semicolon/process/scss/block-value.scss
@@ -0,0 +1 @@
+div { color: tomato; font: { family: fantasy; size: 16px; } }
diff --git a/test/options/always-semicolon/process/scss/condition-multiline.scss b/test/options/always-semicolon/process/scss/condition-multiline.scss
new file mode 100644
index 00000000..7aeb8afa
--- /dev/null
+++ b/test/options/always-semicolon/process/scss/condition-multiline.scss
@@ -0,0 +1,6 @@
+div {
+ $color: tomato;
+ @if $color == tomato {
+ top: 0;
+ }
+ }
diff --git a/test/options/always-semicolon/process/scss/condition.scss b/test/options/always-semicolon/process/scss/condition.scss
new file mode 100644
index 00000000..692cbcae
--- /dev/null
+++ b/test/options/always-semicolon/process/scss/condition.scss
@@ -0,0 +1 @@
+div { $color: tomato; @if $color == tomato { top: 0; } }
diff --git a/test/options/always-semicolon/process/scss/extend-multiline.expected.scss b/test/options/always-semicolon/process/scss/extend-multiline.expected.scss
new file mode 100644
index 00000000..eaaa0c75
--- /dev/null
+++ b/test/options/always-semicolon/process/scss/extend-multiline.expected.scss
@@ -0,0 +1,4 @@
+div {
+ color: tomato;
+ @extend .nani;
+ }
diff --git a/test/options/always-semicolon/process/scss/extend-multiline.scss b/test/options/always-semicolon/process/scss/extend-multiline.scss
new file mode 100644
index 00000000..6493e728
--- /dev/null
+++ b/test/options/always-semicolon/process/scss/extend-multiline.scss
@@ -0,0 +1,4 @@
+div {
+ color: tomato;
+ @extend .nani
+ }
diff --git a/test/options/always-semicolon/process/scss/extend.expected.scss b/test/options/always-semicolon/process/scss/extend.expected.scss
new file mode 100644
index 00000000..ec45b2e0
--- /dev/null
+++ b/test/options/always-semicolon/process/scss/extend.expected.scss
@@ -0,0 +1 @@
+div { color: tomato; @extend .nani; }
diff --git a/test/options/always-semicolon/process/scss/extend.scss b/test/options/always-semicolon/process/scss/extend.scss
new file mode 100644
index 00000000..8f545cc6
--- /dev/null
+++ b/test/options/always-semicolon/process/scss/extend.scss
@@ -0,0 +1 @@
+div { color: tomato; @extend .nani }
diff --git a/test/options/always-semicolon/process/scss/include-1-multiline.expected.scss b/test/options/always-semicolon/process/scss/include-1-multiline.expected.scss
new file mode 100644
index 00000000..d8db3e32
--- /dev/null
+++ b/test/options/always-semicolon/process/scss/include-1-multiline.expected.scss
@@ -0,0 +1,4 @@
+div {
+ color: tomato;
+ @include nani;
+ }
diff --git a/test/options/always-semicolon/process/scss/include-1-multiline.scss b/test/options/always-semicolon/process/scss/include-1-multiline.scss
new file mode 100644
index 00000000..bf95505c
--- /dev/null
+++ b/test/options/always-semicolon/process/scss/include-1-multiline.scss
@@ -0,0 +1,4 @@
+div {
+ color: tomato;
+ @include nani
+ }
diff --git a/test/options/always-semicolon/process/scss/include-1.expected.scss b/test/options/always-semicolon/process/scss/include-1.expected.scss
new file mode 100644
index 00000000..2feaaec8
--- /dev/null
+++ b/test/options/always-semicolon/process/scss/include-1.expected.scss
@@ -0,0 +1 @@
+div { color: tomato; @include nani; }
diff --git a/test/options/always-semicolon/process/scss/include-1.scss b/test/options/always-semicolon/process/scss/include-1.scss
new file mode 100644
index 00000000..204e35d3
--- /dev/null
+++ b/test/options/always-semicolon/process/scss/include-1.scss
@@ -0,0 +1 @@
+div { color: tomato; @include nani }
diff --git a/test/options/always-semicolon/process/scss/include-2-multiline.expected.scss b/test/options/always-semicolon/process/scss/include-2-multiline.expected.scss
new file mode 100644
index 00000000..e0c36008
--- /dev/null
+++ b/test/options/always-semicolon/process/scss/include-2-multiline.expected.scss
@@ -0,0 +1,4 @@
+div {
+ color: tomato;
+ @include nani(10);
+ }
diff --git a/test/options/always-semicolon/process/scss/include-2-multiline.scss b/test/options/always-semicolon/process/scss/include-2-multiline.scss
new file mode 100644
index 00000000..ad05456d
--- /dev/null
+++ b/test/options/always-semicolon/process/scss/include-2-multiline.scss
@@ -0,0 +1,4 @@
+div {
+ color: tomato;
+ @include nani(10)
+ }
diff --git a/test/options/always-semicolon/process/scss/include-2.expected.scss b/test/options/always-semicolon/process/scss/include-2.expected.scss
new file mode 100644
index 00000000..87719565
--- /dev/null
+++ b/test/options/always-semicolon/process/scss/include-2.expected.scss
@@ -0,0 +1 @@
+div { color: tomato; @include nani(10); }
diff --git a/test/options/always-semicolon/process/scss/include-2.scss b/test/options/always-semicolon/process/scss/include-2.scss
new file mode 100644
index 00000000..f5bccc9f
--- /dev/null
+++ b/test/options/always-semicolon/process/scss/include-2.scss
@@ -0,0 +1 @@
+div { color: tomato; @include nani(10) }
diff --git a/test/options/always-semicolon/process/scss/loop-multiline.scss b/test/options/always-semicolon/process/scss/loop-multiline.scss
new file mode 100644
index 00000000..5768e29b
--- /dev/null
+++ b/test/options/always-semicolon/process/scss/loop-multiline.scss
@@ -0,0 +1,6 @@
+div {
+ color: tomato;
+ @while 1 > 2 {
+ top: 0;
+ }
+ }
diff --git a/test/options/always-semicolon/process/scss/loop.scss b/test/options/always-semicolon/process/scss/loop.scss
new file mode 100644
index 00000000..afb46f9c
--- /dev/null
+++ b/test/options/always-semicolon/process/scss/loop.scss
@@ -0,0 +1 @@
+div { color: tomato; @while 1 > 2 { top: 0; } }
diff --git a/test/options/always-semicolon/process/test.js b/test/options/always-semicolon/process/test.js
new file mode 100644
index 00000000..7860c44c
--- /dev/null
+++ b/test/options/always-semicolon/process/test.js
@@ -0,0 +1,171 @@
+let Test = require('../../option_test');
+
+describe('Option `always-semicolon`, process', function() {
+ describe('css', function() {
+ it('Should add semicolon for last property if missing. Test 1', function() {
+ let test = new Test(this, {'always-semicolon': true});
+ return test.shouldBeEqual('test-1.css', 'test-1.expected.css');
+ });
+
+ it('Should add semicolon for last property if missing. Test 2', function() {
+ let test = new Test(this, {'always-semicolon': true});
+ return test.shouldBeEqual('test-2.css', 'test-2.expected.css');
+ });
+
+ it('Should add semicolon for last property if missing. Test 3', function() {
+ let test = new Test(this, {'always-semicolon': true});
+ return test.shouldBeEqual('test-3.css', 'test-3.expected.css');
+ });
+
+ it('Should add semicolon for last property if missing. Test 4', function() {
+ let test = new Test(this, {'always-semicolon': true});
+ return test.shouldBeEqual('test-4.css', 'test-4.expected.css');
+ });
+
+ it('Should add semicolon for last property if missing. Test 5', function() {
+ let test = new Test(this, {'always-semicolon': true});
+ return test.shouldBeEqual('test-5.css', 'test-5.expected.css');
+ });
+ });
+
+ describe('less', function() {
+ it.skip('Should not add semicolon to condition (single-line style)', function() {
+ let test = new Test(this, {'always-semicolon': true});
+ return test.shouldBeEqual('condition.less');
+ });
+
+ it.skip('Should not add semicolon to condition (multi-line style)', function() {
+ let test = new Test(this, {'always-semicolon': true});
+ return test.shouldBeEqual('condition-multiline.less');
+ });
+
+ it('Should add semicolon to last included mixin if missing. Test 1 (single-line style)', function() {
+ let test = new Test(this, {'always-semicolon': true});
+ return test.shouldBeEqual('include-1.less', 'include-1.expected.less');
+ });
+
+ it('Should add semicolon to last included mixin if missing. Test 1 (multi-line style)', function() {
+ let test = new Test(this, {'always-semicolon': true});
+ return test.shouldBeEqual('include-1-multiline.less', 'include-1-multiline.expected.less');
+ });
+
+ it('Should add semicolon to last included mixin if missing. Test 2 (single-line style)', function() {
+ let test = new Test(this, {'always-semicolon': true});
+ return test.shouldBeEqual('include-2.less', 'include-2.expected.less');
+ });
+
+ it('Should add semicolon to last included mixin if missing. Test 2 (multi-line style)', function() {
+ let test = new Test(this, {'always-semicolon': true});
+ return test.shouldBeEqual('include-2-multiline.less', 'include-2-multiline.expected.less');
+ });
+
+ it('Should add semicolon to last included mixin if missing. Test 3 (single-line style)', function() {
+ let test = new Test(this, {'always-semicolon': true});
+ return test.shouldBeEqual('include-3.less', 'include-3.expected.less');
+ });
+
+ it('Should add semicolon to last included mixin if missing. Test 3 (multi-line style)', function() {
+ let test = new Test(this, {'always-semicolon': true});
+ return test.shouldBeEqual('include-3-multiline.less', 'include-3-multiline.expected.less');
+ });
+
+ it('Should add semicolon to last included mixin if missing. Test 4 (single-line style)', function() {
+ let test = new Test(this, {'always-semicolon': true});
+ return test.shouldBeEqual('include-4.less', 'include-4.expected.less');
+ });
+
+ it('Should add semicolon to last included mixin if missing. Test 4 (multi-line style)', function() {
+ let test = new Test(this, {'always-semicolon': true});
+ return test.shouldBeEqual('include-4-multiline.less', 'include-4-multiline.expected.less');
+ });
+
+ it('Should add semicolon to last included mixin if missing. Test 5 (single-line style)', function() {
+ let test = new Test(this, {'always-semicolon': true});
+ return test.shouldBeEqual('include-5.less', 'include-5.expected.less');
+ });
+
+ it('Should add semicolon to last included mixin if missing. Test 5 (multi-line style)', function() {
+ let test = new Test(this, {'always-semicolon': true});
+ return test.shouldBeEqual('include-5-multiline.less', 'include-5-multiline.expected.less');
+ });
+ });
+
+ describe('sass', function() {
+ it('Should not add semicolon', function() {
+ let test = new Test(this, {'always-semicolon': true});
+ return test.shouldBeEqual('test.sass');
+ });
+ });
+
+ describe('scss', function() {
+ it('Should not add semicolon if last value is block (singl-line style)', function() {
+ let test = new Test(this, {'always-semicolon': true});
+ return test.shouldBeEqual('block-value.scss');
+ });
+
+ it('Should not add semicolon if last value is block (multi-line style)', function() {
+ let test = new Test(this, {'always-semicolon': true});
+ return test.shouldBeEqual('block-value-multiline.scss');
+ });
+
+ it('Should add semicolon to last included mixin if missing. Test 1 (single-line style)', function() {
+ let test = new Test(this, {'always-semicolon': true});
+ return test.shouldBeEqual('include-1.scss', 'include-1.expected.scss');
+ });
+
+ it('Should add semicolon to last included mixin if missing. Test 1 (multi-line style)', function() {
+ let test = new Test(this, {'always-semicolon': true});
+ return test.shouldBeEqual('include-1-multiline.scss', 'include-1-multiline.expected.scss');
+ });
+
+ it('Should add semicolon to last included mixin if missing. Test 2 (single-line style)', function() {
+ let test = new Test(this, {'always-semicolon': true});
+ return test.shouldBeEqual('include-2.scss', 'include-2.expected.scss');
+ });
+
+ it('Should add semicolon to last included mixin if missing. Test 2 (multi-line style)', function() {
+ let test = new Test(this, {'always-semicolon': true});
+ return test.shouldBeEqual('include-2-multiline.scss', 'include-2-multiline.expected.scss');
+ });
+
+ it('Should not add semicolon to last included mixin if there is a block (single-line style)', function() {
+ let test = new Test(this, {'always-semicolon': true});
+ return test.shouldBeEqual('block-include.scss');
+ });
+
+ it('Should not add semicolon to last included mixin if there is a block (multi-line style)', function() {
+ let test = new Test(this, {'always-semicolon': true});
+ return test.shouldBeEqual('block-include-multiline.scss');
+ });
+
+ it('Should add semicolon to last extend if missing (single-line style)', function() {
+ let test = new Test(this, {'always-semicolon': true});
+ return test.shouldBeEqual('extend.scss', 'extend.expected.scss');
+ });
+
+ it('Should add semicolon to last extend if missing (multi-line style)', function() {
+ let test = new Test(this, {'always-semicolon': true});
+ return test.shouldBeEqual('extend-multiline.scss', 'extend-multiline.expected.scss');
+ });
+
+ it('Should not add semicolon to condition (single-line style)', function() {
+ let test = new Test(this, {'always-semicolon': true});
+ return test.shouldBeEqual('condition.scss');
+ });
+
+ it('Should not add semicolon to condition (multi-line style)', function() {
+ let test = new Test(this, {'always-semicolon': true});
+ return test.shouldBeEqual('condition-multiline.scss');
+ });
+
+ it('Should not add semicolon to loop (single-line style)', function() {
+ let test = new Test(this, {'always-semicolon': true});
+ return test.shouldBeEqual('loop.scss');
+ });
+
+ it('Should not add semicolon to loop (multi-line style)', function() {
+ let test = new Test(this, {'always-semicolon': true});
+ return test.shouldBeEqual('loop-multiline.scss');
+ });
+ });
+});
diff --git a/test/options/block-indent/detect/css/test-3.expected.css b/test/options/block-indent/detect/css/test-3.expected.css
new file mode 100644
index 00000000..684ff987
--- /dev/null
+++ b/test/options/block-indent/detect/css/test-3.expected.css
@@ -0,0 +1,36 @@
+a {color: tomato; top: 0;
+}
+
+a { color: tomato;
+ top: 0;
+}
+
+a { color: tomato;
+ top: 0;
+}
+
+a {
+ color: tomato;
+ top: 0;
+}
+
+a {
+ color: tomato;
+ top: 0;
+}
+
+a {
+ color: tomato;
+ top: 0;
+}
+
+@media print { a {color: tomato; top: 0;
+ }
+}
+
+@media print {
+ a {
+ color: tomato;
+ top: 0;
+ }
+}
diff --git a/test/options/block-indent/detect/css/test.css b/test/options/block-indent/detect/css/test.css
new file mode 100644
index 00000000..b1b52eb3
--- /dev/null
+++ b/test/options/block-indent/detect/css/test.css
@@ -0,0 +1,30 @@
+a {color: tomato; top: 0;}
+
+a { color: tomato;
+top: 0; }
+
+a { color: tomato;
+ top: 0; }
+
+a {
+color: tomato;
+top: 0; }
+
+a {
+color: tomato;
+top: 0;
+}
+
+ a {
+ color: tomato;
+ top: 0;
+ }
+
+@media print { a {color: tomato; top: 0; } }
+
+ @media print {
+a {
+ color: tomato;
+ top: 0;
+ }
+ }
diff --git a/test/options/block-indent/detect/test.js b/test/options/block-indent/detect/test.js
new file mode 100644
index 00000000..6035c3d8
--- /dev/null
+++ b/test/options/block-indent/detect/test.js
@@ -0,0 +1,49 @@
+let Test = require('../../option_test');
+
+describe('Option `block-indent`, detect', function() {
+ describe('css', function() {
+ it('Should detect nothing with an empty block, test 1', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['block-indent'],
+ 'a{ }',
+ {}
+ );
+ });
+
+ it('Should detect nothing with an empty block, test 2', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['block-indent'],
+ 'a{}',
+ {}
+ );
+ });
+
+ it('Should detect correct number of spaces', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['block-indent'],
+ 'a{\n top: 0;\n color: tomato;\n}',
+ {'block-indent': ' '}
+ );
+ });
+
+ it('Should detect no indent for one-line code', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['block-indent'],
+ 'a{ top: 0; color: tomato; }',
+ {}
+ );
+ });
+
+ it('Valid string value => should set proper space after combnator', function() {
+ let test = new Test(this, {
+ 'block-indent': ' ',
+ 'space-before-closing-brace': '\n'
+ });
+ return test.shouldBeEqual('test.css', 'test-3.expected.css');
+ });
+ });
+});
diff --git a/test/options/block-indent/lint/test.js b/test/options/block-indent/lint/test.js
new file mode 100644
index 00000000..231e4209
--- /dev/null
+++ b/test/options/block-indent/lint/test.js
@@ -0,0 +1,4 @@
+describe('Option `block-indent`, lint', function() {
+ describe('css', function() {
+ });
+});
diff --git a/test/options/block-indent/process/css/issue-379.css b/test/options/block-indent/process/css/issue-379.css
new file mode 100644
index 00000000..46ef2f74
--- /dev/null
+++ b/test/options/block-indent/process/css/issue-379.css
@@ -0,0 +1,3 @@
+.pageArea #header {
+color: red;
+}
diff --git a/test/options/block-indent/process/css/issue-379.expected.css b/test/options/block-indent/process/css/issue-379.expected.css
new file mode 100644
index 00000000..3708595d
--- /dev/null
+++ b/test/options/block-indent/process/css/issue-379.expected.css
@@ -0,0 +1,3 @@
+.pageArea #header {
+ color: red;
+ }
diff --git a/test/options/block-indent/process/css/test-2.expected.css b/test/options/block-indent/process/css/test-2.expected.css
new file mode 100644
index 00000000..467a9326
--- /dev/null
+++ b/test/options/block-indent/process/css/test-2.expected.css
@@ -0,0 +1,30 @@
+a {color: tomato; top: 0;}
+
+a { color: tomato;
+ top: 0; }
+
+a { color: tomato;
+ top: 0; }
+
+a {
+ color: tomato;
+ top: 0; }
+
+a {
+ color: tomato;
+ top: 0;
+ }
+
+a {
+ color: tomato;
+ top: 0;
+ }
+
+@media print { a {color: tomato; top: 0; } }
+
+@media print {
+ a {
+ color: tomato;
+ top: 0;
+ }
+ }
diff --git a/test/options/block-indent/process/css/test-3.expected.css b/test/options/block-indent/process/css/test-3.expected.css
new file mode 100644
index 00000000..684ff987
--- /dev/null
+++ b/test/options/block-indent/process/css/test-3.expected.css
@@ -0,0 +1,36 @@
+a {color: tomato; top: 0;
+}
+
+a { color: tomato;
+ top: 0;
+}
+
+a { color: tomato;
+ top: 0;
+}
+
+a {
+ color: tomato;
+ top: 0;
+}
+
+a {
+ color: tomato;
+ top: 0;
+}
+
+a {
+ color: tomato;
+ top: 0;
+}
+
+@media print { a {color: tomato; top: 0;
+ }
+}
+
+@media print {
+ a {
+ color: tomato;
+ top: 0;
+ }
+}
diff --git a/test/options/block-indent/process/css/test.css b/test/options/block-indent/process/css/test.css
new file mode 100644
index 00000000..b1b52eb3
--- /dev/null
+++ b/test/options/block-indent/process/css/test.css
@@ -0,0 +1,30 @@
+a {color: tomato; top: 0;}
+
+a { color: tomato;
+top: 0; }
+
+a { color: tomato;
+ top: 0; }
+
+a {
+color: tomato;
+top: 0; }
+
+a {
+color: tomato;
+top: 0;
+}
+
+ a {
+ color: tomato;
+ top: 0;
+ }
+
+@media print { a {color: tomato; top: 0; } }
+
+ @media print {
+a {
+ color: tomato;
+ top: 0;
+ }
+ }
diff --git a/test/options/block-indent/process/css/test.expected.css b/test/options/block-indent/process/css/test.expected.css
new file mode 100644
index 00000000..0c684ec8
--- /dev/null
+++ b/test/options/block-indent/process/css/test.expected.css
@@ -0,0 +1,30 @@
+a {color: tomato; top: 0;}
+
+a { color: tomato;
+top: 0; }
+
+a { color: tomato;
+top: 0; }
+
+a {
+color: tomato;
+top: 0; }
+
+a {
+color: tomato;
+top: 0;
+}
+
+a {
+color: tomato;
+top: 0;
+}
+
+@media print { a {color: tomato; top: 0; } }
+
+@media print {
+a {
+color: tomato;
+top: 0;
+}
+}
diff --git a/test/options/block-indent/process/sass/block.expected.sass b/test/options/block-indent/process/sass/block.expected.sass
new file mode 100644
index 00000000..ddae383f
--- /dev/null
+++ b/test/options/block-indent/process/sass/block.expected.sass
@@ -0,0 +1,6 @@
+a
+ color: tomato
+ top: 0
+
+p
+ bottom: 0
diff --git a/test/options/block-indent/process/sass/block.sass b/test/options/block-indent/process/sass/block.sass
new file mode 100644
index 00000000..9be6816f
--- /dev/null
+++ b/test/options/block-indent/process/sass/block.sass
@@ -0,0 +1,6 @@
+a
+ color: tomato
+ top: 0
+
+p
+ bottom: 0
diff --git a/test/options/block-indent/process/sass/mixin.expected.sass b/test/options/block-indent/process/sass/mixin.expected.sass
new file mode 100644
index 00000000..f9351138
--- /dev/null
+++ b/test/options/block-indent/process/sass/mixin.expected.sass
@@ -0,0 +1,8 @@
+@mixin sexy-border($color, $width: 1px)
+ border:
+ color: $color
+ width: $width
+ style: dashed
+
+p
+ +sexy-border(blue)
diff --git a/test/options/block-indent/process/sass/mixin.sass b/test/options/block-indent/process/sass/mixin.sass
new file mode 100644
index 00000000..d25771d9
--- /dev/null
+++ b/test/options/block-indent/process/sass/mixin.sass
@@ -0,0 +1,8 @@
+@mixin sexy-border($color, $width: 1px)
+ border:
+ color: $color
+ width: $width
+ style: dashed
+
+p
+ +sexy-border(blue)
diff --git a/test/options/block-indent/process/sass/nested-ruleset.expected.sass b/test/options/block-indent/process/sass/nested-ruleset.expected.sass
new file mode 100644
index 00000000..2c7edc6c
--- /dev/null
+++ b/test/options/block-indent/process/sass/nested-ruleset.expected.sass
@@ -0,0 +1,9 @@
+a
+ color: tomato
+ top: 0
+
+ p
+ bottom: 0
+
+ &:hover
+ color: yellow
diff --git a/test/options/block-indent/process/sass/nested-ruleset.sass b/test/options/block-indent/process/sass/nested-ruleset.sass
new file mode 100644
index 00000000..8567d2e5
--- /dev/null
+++ b/test/options/block-indent/process/sass/nested-ruleset.sass
@@ -0,0 +1,9 @@
+a
+ color: tomato
+ top: 0
+
+ p
+ bottom: 0
+
+ &:hover
+ color: yellow
diff --git a/test/options/block-indent/process/scss/nested-include.expected.scss b/test/options/block-indent/process/scss/nested-include.expected.scss
new file mode 100644
index 00000000..c419883d
--- /dev/null
+++ b/test/options/block-indent/process/scss/nested-include.expected.scss
@@ -0,0 +1,5 @@
+li{
+ @include respond-to(mobile){ float: none; }
+ float: left;
+ width: 25%;
+ }
diff --git a/test/options/block-indent/process/scss/nested-include.scss b/test/options/block-indent/process/scss/nested-include.scss
new file mode 100644
index 00000000..93504be6
--- /dev/null
+++ b/test/options/block-indent/process/scss/nested-include.scss
@@ -0,0 +1,5 @@
+li{
+ @include respond-to(mobile){ float: none; }
+ float: left;
+ width: 25%;
+}
diff --git a/test/options/block-indent/process/test.js b/test/options/block-indent/process/test.js
new file mode 100644
index 00000000..3f61e4a6
--- /dev/null
+++ b/test/options/block-indent/process/test.js
@@ -0,0 +1,59 @@
+let Test = require('../../option_test');
+
+describe('Option `block-indent`, process', function() {
+ describe('css', function() {
+ it('Array value => should not change anything', function() {
+ let test = new Test(this, {'block-indent': ['', ' ']});
+ return test.shouldBeEqual('test.css');
+ });
+
+ it('Invalid string value => should not change anything', function() {
+ let test = new Test(this, {'block-indent': ' nani '});
+ return test.shouldBeEqual('test.css');
+ });
+
+ it('Float number value => should not change anything', function() {
+ let test = new Test(this, {'block-indent': 3.5});
+ return test.shouldBeEqual('test.css');
+ });
+
+ it('Integer value => should set proper number of spaces', function() {
+ let test = new Test(this, {'block-indent': 0});
+ return test.shouldBeEqual('test.css', 'test.expected.css');
+ });
+
+ it('Valid string value => should set proper number of spaces', function() {
+ let test = new Test(this, {'block-indent': ' '});
+ return test.shouldBeEqual('test.css', 'test-2.expected.css');
+ });
+
+ it('Issue 379', function() {
+ let test = new Test(this, {'block-indent': 4});
+ return test.shouldBeEqual('issue-379.css', 'issue-379.expected.css');
+ });
+ });
+
+ describe('sass', function() {
+ it('First level ruleset\'s block', function() {
+ let test = new Test(this, {'block-indent': 2});
+ return test.shouldBeEqual('block.sass', 'block.expected.sass');
+ });
+
+ it('Nested ruleset', function() {
+ let test = new Test(this, {'block-indent': 2});
+ return test.shouldBeEqual('nested-ruleset.sass', 'nested-ruleset.expected.sass');
+ });
+
+ it('Mixin', function() {
+ let test = new Test(this, {'block-indent': 4});
+ return test.shouldBeEqual('mixin.sass', 'mixin.expected.sass');
+ });
+ });
+
+ describe('scss', function() {
+ it('Issue 213', function() {
+ let test = new Test(this, {'block-indent': 2});
+ return test.shouldBeEqual('nested-include.scss', 'nested-include.expected.scss');
+ });
+ });
+});
diff --git a/test/options/color-case/detect/test.js b/test/options/color-case/detect/test.js
new file mode 100644
index 00000000..cc075c37
--- /dev/null
+++ b/test/options/color-case/detect/test.js
@@ -0,0 +1,59 @@
+let Test = require('../../option_test');
+
+describe('Option `block-indent`, detect', function() {
+ describe('css', function() {
+ it('Should detect uppercase color', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['color-case'],
+ 'a { color: #F3F3F3 }',
+ {'color-case': 'upper'}
+ );
+ });
+
+ it('Should detect lowercase color', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['color-case'],
+ 'a { color: #f6f6f6 }',
+ {'color-case': 'lower'}
+ );
+ });
+
+ it('Should detect uppercase color in a shorthand', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['color-case'],
+ 'a { color: #FFF }',
+ {'color-case': 'upper'}
+ );
+ });
+
+ it('Should detect lowercase color in a shorthand', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['color-case'],
+ 'a { color: #fff }',
+ {'color-case': 'lower'}
+ );
+ });
+
+ it('Shouldn’t detect color case if it contains only digits', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['color-case'],
+ 'a { color: #333 }',
+ {}
+ );
+ });
+
+ it('Shouldn’t detect color case if it is in mixed case', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['color-case'],
+ 'a { color: #fFfFfF }',
+ {}
+ );
+ });
+ });
+});
diff --git a/test/options/color-case/lint/test.js b/test/options/color-case/lint/test.js
new file mode 100644
index 00000000..e69de29b
diff --git a/test/options/color-case/process/test.js b/test/options/color-case/process/test.js
new file mode 100644
index 00000000..3b8ae697
--- /dev/null
+++ b/test/options/color-case/process/test.js
@@ -0,0 +1,40 @@
+let assert = require('assert');
+let Test = require('../../option_test');
+
+describe('Option `color-case`, process', function() {
+ describe('css', function() {
+ it('Should switch colors to upper case', function() {
+ let test = new Test(this, {'color-case': 'upper'});
+ return test.comb.processString('div { color: #fff }')
+ .then(actual => {
+ assert.equal(actual, 'div { color: #FFF }');
+ });
+ });
+
+ it('Should switch colors to lower case', function() {
+ let test = new Test(this, {'color-case': 'lower'});
+ return test.comb.processString('div { color: #FFF }')
+ .then(actual => {
+ assert.equal(actual, 'div { color: #fff }');
+ });
+ });
+
+ it('Should switch color-case in complex rules', function() {
+ let test = new Test(this, {'color-case': 'lower'});
+ return test.comb.processString(
+ 'div { background: url(img.png#RND) #E3E3E3 0 100% no-repeat;' +
+ ' box-shadow: 1px 2px 3px 4px #F0F0F0 inset; }'
+ ).then(actual => {
+ assert.equal(actual, 'div { background: url(img.png#RND) #e3e3e3 0 100% no-repeat; box-shadow: 1px 2px 3px 4px #f0f0f0 inset; }');
+ });
+ });
+
+ it('Should not switch selector case', function() {
+ let test = new Test(this, {'color-case': 'lower'});
+ return test.comb.processString('#Header { color: #FFF }')
+ .then(function(actual) {
+ assert.equal(actual, '#Header { color: #fff }');
+ });
+ });
+ });
+});
diff --git a/test/options/color-shorthand/detect/css b/test/options/color-shorthand/detect/css
new file mode 100644
index 00000000..e69de29b
diff --git a/test/options/color-shorthand/detect/test.js b/test/options/color-shorthand/detect/test.js
new file mode 100644
index 00000000..59e46933
--- /dev/null
+++ b/test/options/color-shorthand/detect/test.js
@@ -0,0 +1,41 @@
+let Test = require('../../option_test');
+
+describe('Option `color-shorthand`, detect', function() {
+ describe('css', function() {
+ it('Should detect non-shorthanded color', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['color-shorthand'],
+ 'a { color: #FF33EE }',
+ {'color-shorthand': false}
+ );
+ });
+
+ it('Should detect shorthanded color', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['color-shorthand'],
+ 'a { color: #fff }',
+ {'color-shorthand': true}
+ );
+ });
+
+ it('Shouldn’t detect if a color is shorthanded if it can’t be shorthanded', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['color-shorthand'],
+ 'a { color: #F3F3F3 }',
+ {}
+ );
+ });
+
+ it('Shouldn’t detect if a color is shorthanded if it is not a vhash', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['color-shorthand'],
+ 'a { color: rgba(0,0,0,0.5) }',
+ {}
+ );
+ });
+ });
+});
diff --git a/test/options/color-shorthand/lint/css b/test/options/color-shorthand/lint/css
new file mode 100644
index 00000000..e69de29b
diff --git a/test/options/color-shorthand/lint/test.js b/test/options/color-shorthand/lint/test.js
new file mode 100644
index 00000000..e69de29b
diff --git a/test/options/color-shorthand/process/test.js b/test/options/color-shorthand/process/test.js
new file mode 100644
index 00000000..b9b1be7a
--- /dev/null
+++ b/test/options/color-shorthand/process/test.js
@@ -0,0 +1,33 @@
+var assert = require('assert');
+let Test = require('../../option_test');
+
+describe('Option `color-shorthand`, process', function() {
+ describe('css', function() {
+ it('Should shrink hexadecimal colors to 3 symbols', function() {
+ let test = new Test(this, {'color-shorthand': true});
+ return test.comb.processString(
+ 'div { color: #aabbcc }'
+ ).then(function(actual) {
+ assert.equal(actual, 'div { color: #abc }');
+ });
+ });
+
+ it('Should expand hexadecimal colors to 6 symbols', function() {
+ let test = new Test(this, {'color-shorthand': false});
+ return test.comb.processString(
+ 'div { color: #7ad }'
+ ).then(function(actual) {
+ assert.equal(actual, 'div { color: #77aadd }');
+ });
+ });
+
+ it('Should save case while processing', function() {
+ let test = new Test(this, {'color-shorthand': true});
+ return test.comb.processString(
+ 'div { color: #fFAafF }'
+ ).then(function(actual) {
+ assert.equal(actual, 'div { color: #fAf }');
+ });
+ });
+ });
+});
diff --git a/test/options/element-case/detect/test.js b/test/options/element-case/detect/test.js
new file mode 100644
index 00000000..22cc8bbf
--- /dev/null
+++ b/test/options/element-case/detect/test.js
@@ -0,0 +1,59 @@
+let Test = require('../../option_test');
+
+describe('Option `element-case`, detect', function() {
+ describe('css', function() {
+ it('Should detect lowercase elements', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['element-case'],
+ 'a { color: red }',
+ {'element-case': 'lower'}
+ );
+ });
+
+ it('Should detect uppercase elements', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['element-case'],
+ 'A { color: red }',
+ {'element-case': 'upper'}
+ );
+ });
+
+ it('Should detect lowercase elements in a long selector', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['element-case'],
+ 'ul li:not(:hover) A { color: red }',
+ {'element-case': 'lower'}
+ );
+ });
+
+ it('Should detect uppercase elements in a long selector', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['element-case'],
+ 'ul .lol:not(LI) A { color: red }',
+ {'element-case': 'upper'}
+ );
+ });
+
+ it('Shouldn’t detect case of elements in a mixed case', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['element-case'],
+ 'aRtIcLe { color: red }',
+ {}
+ );
+ });
+
+ it('Shouldn’t detect case of elements when there are no such', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['element-case'],
+ '*.lol { color: red }',
+ {}
+ );
+ });
+ });
+});
diff --git a/test/options/element-case/lint/test.js b/test/options/element-case/lint/test.js
new file mode 100644
index 00000000..e69de29b
diff --git a/test/options/element-case/process/scss/mixin.expected.scss b/test/options/element-case/process/scss/mixin.expected.scss
new file mode 100644
index 00000000..8700a0ea
--- /dev/null
+++ b/test/options/element-case/process/scss/mixin.expected.scss
@@ -0,0 +1,3 @@
+a {
+ @include boxSizingContent();
+}
diff --git a/test/options/element-case/process/scss/mixin.scss b/test/options/element-case/process/scss/mixin.scss
new file mode 100644
index 00000000..d2b5c33e
--- /dev/null
+++ b/test/options/element-case/process/scss/mixin.scss
@@ -0,0 +1,3 @@
+A {
+ @include boxSizingContent();
+}
diff --git a/test/options/element-case/process/test.js b/test/options/element-case/process/test.js
new file mode 100644
index 00000000..350970fe
--- /dev/null
+++ b/test/options/element-case/process/test.js
@@ -0,0 +1,49 @@
+var assert = require('assert');
+let Test = require('../../option_test');
+
+describe('Option `element-case`, process', function() {
+ describe('css', function() {
+ it('Invalid String should not change case of elements', function() {
+ let test = new Test(this, {'element-case': 'foobar'});
+ return test.comb.processString(
+ 'LI a { color : red }'
+ ).then(function(actual) {
+ assert.equal(actual, 'LI a { color : red }');
+ });
+ });
+
+ it('Should switch tag name to upper case', function() {
+ let test = new Test(this, {'element-case': 'upper'});
+ return test.comb.processString(
+ 'div { color: #fff }'
+ ).then(function(actual) {
+ assert.equal(actual, 'DIV { color: #fff }');
+ });
+ });
+
+ it('Should switch tag name to lower case', function() {
+ let test = new Test(this, {'element-case': 'lower'});
+ return test.comb.processString(
+ 'DIV { color: #FFF }'
+ ).then(function(actual) {
+ assert.equal(actual, 'div { color: #FFF }');
+ });
+ });
+
+ it('Should switch element-case in complex rules', function() {
+ let test = new Test(this, {'element-case': 'lower'});
+ return test.comb.processString(
+ 'UL > LI > .foo:not(A) { color: red }'
+ ).then(function(actual) {
+ assert.equal(actual, 'ul > li > .foo:not(a) { color: red }');
+ });
+ });
+ });
+
+ describe('scss', function() {
+ it('Should not touch mixin names', function() {
+ let test = new Test(this, {'element-case': 'lower'});
+ return test.shouldBeEqual('mixin.scss', 'mixin.expected.scss');
+ });
+ });
+});
diff --git a/test/options/eof-newline/detect/test.js b/test/options/eof-newline/detect/test.js
new file mode 100644
index 00000000..c7c6cd80
--- /dev/null
+++ b/test/options/eof-newline/detect/test.js
@@ -0,0 +1,42 @@
+let Test = require('../../option_test');
+
+describe('Option `eof-newline`, detect', function() {
+ describe('css', function() {
+ it('Shouldn’t detect eof newline', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['eof-newline'],
+ 'a { color: red }',
+ {'eof-newline': false}
+ );
+ });
+
+ it('Should detect eof newline', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['eof-newline'],
+ 'a { color: red }\n',
+ {'eof-newline': true}
+ );
+ });
+
+ it('Shouldn’t detect eof newline with spaces at the end', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['eof-newline'],
+ 'a { color: red } ',
+ {'eof-newline': false}
+ );
+ });
+
+ it('Should detect eof newline with mixed spaces at the end', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['eof-newline'],
+ 'a { color: red } \n ',
+ {'eof-newline': true}
+ );
+ });
+ });
+});
+
diff --git a/test/options/eof-newline/lint/test.js b/test/options/eof-newline/lint/test.js
new file mode 100644
index 00000000..e69de29b
diff --git a/test/options/eof-newline/process/test.js b/test/options/eof-newline/process/test.js
new file mode 100644
index 00000000..3991f373
--- /dev/null
+++ b/test/options/eof-newline/process/test.js
@@ -0,0 +1,33 @@
+var assert = require('assert');
+let Test = require('../../option_test');
+
+describe('Option `eof-newline`, process', function() {
+ describe('css', function() {
+ it('Invalid value should not change trim trailing brac', function() {
+ let test = new Test(this, {'eof-newline': 'foobar'});
+ return test.comb.processString(
+ 'a { color: red } \n'
+ ).then(function(actual) {
+ assert.equal(actual, 'a { color: red } \n');
+ });
+ });
+
+ it('Boolean true value should insert line-break at eof', function() {
+ let test = new Test(this, {'eof-newline': true});
+ return test.comb.processString(
+ 'a {color:red} '
+ ).then(function(actual) {
+ assert.equal(actual, 'a {color:red} \n');
+ });
+ });
+
+ it('Boolean false value should remove line-break from eof', function() {
+ let test = new Test(this, {'eof-newline': false});
+ return test.comb.processString(
+ 'a {color:red} \n'
+ ).then(function(actual) {
+ assert.equal(actual, 'a {color:red} ');
+ });
+ });
+ });
+});
diff --git a/test/integral.expect.css b/test/options/integral/detect/css/integral.expected.css
similarity index 89%
rename from test/integral.expect.css
rename to test/options/integral/detect/css/integral.expected.css
index 9d161b4d..f59d0a21 100644
--- a/test/integral.expect.css
+++ b/test/options/integral/detect/css/integral.expected.css
@@ -13,8 +13,10 @@
}
/* :after — фон */
- .radio-button_theme_normal .radio-button__radio:after
+ .radio-button_theme_normal .radio-button__radio[class^='radio']:after
{
+ content: 'it\'s something different';
+
background: #fff;
background: -webkit-linear-gradient(top, #fff 0,#eee 100%);
background: -moz-linear-gradient(top, #fff 0, #eee 100%);
@@ -25,13 +27,17 @@
/* _focused_yes */
.radio-button_theme_normal .radio-button__radio_focused_yes:before
{
+ content: 'hello';
+
-moz-box-shadow: 0 0 6px 2px rgba(255,204,0,.7), 0 1px 0 rgba(0,0,0,.07);
box-shadow: 0 0 6px 2px rgba(255,204,0,.7), 0 1px 0 rgba(0,0,0,.07);
}
}
/* Фигурные скобки. Вариант 1 */
-a, b, i /* foobar */
+a,
+b,
+i /* foobar */
{
margin: 0;
padding: 0;
@@ -64,7 +70,8 @@ div p em
}
@media all /* media */
-{ /* foobar */
+{
+ /* foobar */
.input__control
{
font-size: 100%;
@@ -81,7 +88,8 @@ div p em
@media screen and (min-width: 35em) /* screen */,
print and (min-width: 40em) /* print */
-{ /* foobar */
+{
+ /* foobar */
.input__control
{
-moz-box-sizing: border-box;
@@ -119,8 +127,7 @@ div
margin: 0;
padding: 0;
}
-/* foo */
-div ~ p
+/* foo */ div ~ p
{
font-size: 1px;
@@ -128,7 +135,7 @@ div ~ p
}
div > p + em
{
- /* upline comment*/
+ /* upline comment*/
font-style: italic;
border-bottom: 1px solid red; /* trololo */ /* trololo */
@@ -136,13 +143,9 @@ div > p + em
a:not(b)
{
- top: 0;/* ololo */
-
- margin: 0;
+ top: 0;/* ololo */margin: 0;
}
b
{
- top: 0/* trololo */;
-
- margin: 0;
+ top: 0/* trololo */;margin: 0;
}
diff --git a/test/options/integral/detect/test.js b/test/options/integral/detect/test.js
new file mode 100644
index 00000000..e4f40a28
--- /dev/null
+++ b/test/options/integral/detect/test.js
@@ -0,0 +1,15 @@
+let Test = require('../../option_test');
+
+describe('Option `integral`, detect', function() {
+ describe('css', function() {
+ it('Should detect everything in integral test', function() {
+ let test = new Test(this);
+ let input = test.readFile('integral.expected.css');
+ // Clone the required config object, otherwise other tests would fail.
+ let expected = JSON.parse(JSON.stringify(test.Comb.getConfig('csscomb')));
+ delete expected['sort-order'];
+ delete expected.exclude;
+ test.shouldDetect(undefined, input, expected);
+ });
+ });
+});
diff --git a/test/options/integral/lint/test.js b/test/options/integral/lint/test.js
new file mode 100644
index 00000000..e69de29b
diff --git a/test/integral.origin.css b/test/options/integral/process/css/integral.css
similarity index 95%
rename from test/integral.origin.css
rename to test/options/integral/process/css/integral.css
index 5c126da3..cb280a33 100644
--- a/test/integral.origin.css
+++ b/test/options/integral/process/css/integral.css
@@ -12,18 +12,20 @@
}
/* :after — фон */
- .radio-button_theme_normal .radio-button__radio:after
+ .radio-button_theme_normal .radio-button__radio[class^="radio"]:after
{
background: #fFf;
background: -webkit-linear-gradient(top, #FffFff 0,#eeeeEe 100%);
background: -moz-linear-gradient(top, #fff 0, #eEe 100%);
background: -o-linear-gradient(top, #fff 0,#eee 100%);
background: linear-gradient(to bottom, #ffffff 0,#eeEeee 100%);
+ content: "it's something different";
}
/* _focused_yes */
.radio-button_theme_normal .radio-button__radio_focused_yes:before
{
+ content: "hello";
-moz-box-shadow: 0 0 6px 2px rgba(255,204,0,.7), 0px 1px 0px rgba(0,0,0,.07);
box-shadow: 0 0 6px 2px rgba(255,204,0,.7), 0px 1px 0px rgba(0,0,0,.07);
}
diff --git a/test/options/integral/process/css/integral.expected.css b/test/options/integral/process/css/integral.expected.css
new file mode 100644
index 00000000..f59d0a21
--- /dev/null
+++ b/test/options/integral/process/css/integral.expected.css
@@ -0,0 +1,151 @@
+/* foobar */
+@media all and (min-width:0)
+{
+ .radio-button_theme_normal .radio-button__radio:before
+ {
+ background: rgba(0,0,0,.4);
+ background: -webkit-linear-gradient(top, rgba(0,0,0,.2) 0,rgba(0,0,0,.4) 100%);
+ background: -moz-linear-gradient(top, rgba(0,0,0,.2) 0, rgba(0,0,0,.4) 100%);
+ background: -o-linear-gradient(top, rgba(0,0,0,.2) 0,rgba(0,0,0,.4) 100%);
+ background: linear-gradient(to bottom, rgba(0,0,0,.2) 0,rgba(0,0,0,.4) 100%);
+ -moz-box-shadow: 0 1px 0 rgba(0,0,0,.07);
+ box-shadow: 0 1px 0 rgba(0,0,0,.07);
+ }
+
+ /* :after — фон */
+ .radio-button_theme_normal .radio-button__radio[class^='radio']:after
+ {
+ content: 'it\'s something different';
+
+ background: #fff;
+ background: -webkit-linear-gradient(top, #fff 0,#eee 100%);
+ background: -moz-linear-gradient(top, #fff 0, #eee 100%);
+ background: -o-linear-gradient(top, #fff 0,#eee 100%);
+ background: linear-gradient(to bottom, #fff 0,#eee 100%);
+ }
+
+ /* _focused_yes */
+ .radio-button_theme_normal .radio-button__radio_focused_yes:before
+ {
+ content: 'hello';
+
+ -moz-box-shadow: 0 0 6px 2px rgba(255,204,0,.7), 0 1px 0 rgba(0,0,0,.07);
+ box-shadow: 0 0 6px 2px rgba(255,204,0,.7), 0 1px 0 rgba(0,0,0,.07);
+ }
+}
+
+/* Фигурные скобки. Вариант 1 */
+a,
+b,
+i /* foobar */
+{
+ margin: 0;
+ padding: 0;
+}
+div p
+{
+ font-size: 1px;
+
+ top: 0;
+}
+div p em
+{
+ font-style: italic;
+
+ border-bottom: 1px solid red;
+}
+
+@media all and (min-width:0)
+{
+ /* В нажатом состоянии смещается вниз на 1px вся кнопка, текст не смещается */
+ .button_pressed_yes.button_shadow_yes
+ {
+ top: 1px;
+ }
+
+ .button_pressed_yes.button_shadow_yes .button__text
+ {
+ top: 0;
+ }
+}
+
+@media all /* media */
+{
+ /* foobar */
+ .input__control
+ {
+ font-size: 100%;
+
+ position: relative;
+ z-index: 3;
+
+ width: 100%;
+ margin: 0;
+
+ color: #000;
+ }
+}
+
+@media screen and (min-width: 35em) /* screen */,
+ print and (min-width: 40em) /* print */
+{
+ /* foobar */
+ .input__control
+ {
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ padding: .4em 0;
+
+ border: 0;
+ outline: 0;
+ background: none;
+ }
+}
+
+/* Фигурные скобки. Вариант 2 */
+div
+{
+ margin: 0;
+ padding: 0;
+}
+div p
+{
+ font-size: 1px;
+
+ top: 0;
+}
+div p em
+{
+ font-style: italic;/* inline comment*/
+
+ border-bottom: 1px solid red;
+}
+
+/* Фигурные скобки. Вариант 3 */
+div
+{
+ margin: 0;
+ padding: 0;
+}
+/* foo */ div ~ p
+{
+ font-size: 1px;
+
+ top: 0;
+}
+div > p + em
+{
+ /* upline comment*/
+ font-style: italic;
+
+ border-bottom: 1px solid red; /* trololo */ /* trololo */
+}
+
+a:not(b)
+{
+ top: 0;/* ololo */margin: 0;
+}
+b
+{
+ top: 0/* trololo */;margin: 0;
+}
diff --git a/test/options/integral/process/css/issue-374.css b/test/options/integral/process/css/issue-374.css
new file mode 100644
index 00000000..bf667383
--- /dev/null
+++ b/test/options/integral/process/css/issue-374.css
@@ -0,0 +1,4 @@
+html /deep/ span
+{
+ display: block;
+}
diff --git a/test/options/integral/process/sass/issue-252.expected.sass b/test/options/integral/process/sass/issue-252.expected.sass
new file mode 100644
index 00000000..f3fb55f5
--- /dev/null
+++ b/test/options/integral/process/sass/issue-252.expected.sass
@@ -0,0 +1,11 @@
+.partner
+ font-size: 0
+
+ color: transparent
+
+ +inline-block
+ &--motor
+ background-image: url('/i/partners1.png')
+ &--motor
+ background-image: url('/i/partners2.png')
+
diff --git a/test/options/integral/process/sass/issue-252.sass b/test/options/integral/process/sass/issue-252.sass
new file mode 100644
index 00000000..6369eefd
--- /dev/null
+++ b/test/options/integral/process/sass/issue-252.sass
@@ -0,0 +1,8 @@
+.partner
+ +inline-block
+ font-size: 0
+ color: transparent
+ &--motor
+ background-image: url('/i/partners1.png')
+ &--motor
+ background-image: url('/i/partners2.png')
diff --git a/test/options/integral/process/test.js b/test/options/integral/process/test.js
new file mode 100644
index 00000000..6cf8055e
--- /dev/null
+++ b/test/options/integral/process/test.js
@@ -0,0 +1,25 @@
+let Test = require('../../option_test');
+
+describe('Option `integral`, process', function() {
+ describe('css', function() {
+ it('Process result must be equal to expected.css', function() {
+ let test = new Test(this);
+ test.useConfig('csscomb');
+ return test.shouldBeEqual('integral.css', 'integral.expected.css');
+ });
+
+ it('Issue 374', function() {
+ let test = new Test(this);
+ test.useConfig('csscomb');
+ return test.shouldBeEqual('issue-374.css');
+ });
+ });
+
+ describe('sass', function() {
+ it('Issue 252', function() {
+ let test = new Test(this);
+ test.useConfig('csscomb');
+ return test.shouldBeEqual('issue-252.sass', 'issue-252.expected.sass');
+ });
+ });
+});
diff --git a/test/options/leading-zero/detect/test.js b/test/options/leading-zero/detect/test.js
new file mode 100644
index 00000000..7656ea99
--- /dev/null
+++ b/test/options/leading-zero/detect/test.js
@@ -0,0 +1,33 @@
+let Test = require('../../option_test');
+
+describe('Option `leading-zero`, detect', function() {
+ describe('css', function() {
+ it('Should detect leading zero option', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['leading-zero'],
+ 'a { width: 0.5em }',
+ {'leading-zero': true}
+ );
+ });
+
+ it('Should detect leading zero option set to false', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['leading-zero'],
+ 'a { width: .5em }',
+ {'leading-zero': false}
+ );
+ });
+
+ it('Shouldn’t detect leading zero option', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['leading-zero'],
+ 'a { width: 10.5em }',
+ {}
+ );
+ });
+ });
+});
+
diff --git a/test/options/leading-zero/lint/test.js b/test/options/leading-zero/lint/test.js
new file mode 100644
index 00000000..e69de29b
diff --git a/test/options/leading-zero/process/test.js b/test/options/leading-zero/process/test.js
new file mode 100644
index 00000000..d6eef264
--- /dev/null
+++ b/test/options/leading-zero/process/test.js
@@ -0,0 +1,24 @@
+var assert = require('assert');
+let Test = require('../../option_test');
+
+describe('Option `leading-zero`, process', function() {
+ describe('css', function() {
+ it('Should add leading zero in dimensions', function() {
+ let test = new Test(this, {'leading-zero': true});
+ return test.comb.processString(
+ 'div { margin: .5em }'
+ ).then(function(actual) {
+ assert.equal(actual, 'div { margin: 0.5em }');
+ });
+ });
+
+ it('Should remove leading zero in dimensions', function() {
+ let test = new Test(this, {'leading-zero': false});
+ return test.comb.processString(
+ 'div { margin: 0.5em }'
+ ).then(function(actual) {
+ assert.equal(actual, 'div { margin: .5em }');
+ });
+ });
+ });
+});
diff --git a/test/options/lines-between-rulesets/process/css/2-lines-between-rulesets.expected.css b/test/options/lines-between-rulesets/process/css/2-lines-between-rulesets.expected.css
new file mode 100644
index 00000000..58bbdff5
--- /dev/null
+++ b/test/options/lines-between-rulesets/process/css/2-lines-between-rulesets.expected.css
@@ -0,0 +1,7 @@
+.foo {background: red;}
+
+
+/*comment*/.bar{border: 1px solid red;}
+
+
+.baz{color: #fff;}
diff --git a/test/options/lines-between-rulesets/process/css/lines-between-rulesets.css b/test/options/lines-between-rulesets/process/css/lines-between-rulesets.css
new file mode 100644
index 00000000..30f35c39
--- /dev/null
+++ b/test/options/lines-between-rulesets/process/css/lines-between-rulesets.css
@@ -0,0 +1 @@
+.foo {background: red;}/*comment*/.bar{border: 1px solid red;}.baz{color: #fff;}
diff --git a/test/options/lines-between-rulesets/process/css/lines-between-rulesets.expected.css b/test/options/lines-between-rulesets/process/css/lines-between-rulesets.expected.css
new file mode 100644
index 00000000..ad0651d5
--- /dev/null
+++ b/test/options/lines-between-rulesets/process/css/lines-between-rulesets.expected.css
@@ -0,0 +1,5 @@
+.foo {background: red;}
+
+/*comment*/.bar{border: 1px solid red;}
+
+.baz{color: #fff;}
diff --git a/test/options/lines-between-rulesets/process/less/2-lines-between-rulesets.expected.less b/test/options/lines-between-rulesets/process/less/2-lines-between-rulesets.expected.less
new file mode 100644
index 00000000..7e8b5e9e
--- /dev/null
+++ b/test/options/lines-between-rulesets/process/less/2-lines-between-rulesets.expected.less
@@ -0,0 +1,40 @@
+.foo {
+ .border-radius(5px);
+ background: red;
+
+
+ /* comment */
+ .omg {
+ .test {
+ height: 50px;
+ }
+ }
+}
+
+
+.bar {
+ border: 1px solid red;
+
+
+ /*
+ ** another comment
+ ** spanning multiple lines
+ */
+ @media (min-width: 500px) {
+ width: 50px;
+ }
+}
+
+
+.baz {
+ @grey: #ccc;
+
+
+ // a single-line comment
+ // another single-line comment
+ .wtf {
+ @media only screen {
+ background-color: @grey;
+ }
+ }
+}
diff --git a/test/options/lines-between-rulesets/process/less/lines-between-rulesets.expected.less b/test/options/lines-between-rulesets/process/less/lines-between-rulesets.expected.less
new file mode 100644
index 00000000..ef3a7ef3
--- /dev/null
+++ b/test/options/lines-between-rulesets/process/less/lines-between-rulesets.expected.less
@@ -0,0 +1,35 @@
+.foo {
+ .border-radius(5px);
+ background: red;
+
+ /* comment */
+ .omg {
+ .test {
+ height: 50px;
+ }
+ }
+}
+
+.bar {
+ border: 1px solid red;
+
+ /*
+ ** another comment
+ ** spanning multiple lines
+ */
+ @media (min-width: 500px) {
+ width: 50px;
+ }
+}
+
+.baz {
+ @grey: #ccc;
+
+ // a single-line comment
+ // another single-line comment
+ .wtf {
+ @media only screen {
+ background-color: @grey;
+ }
+ }
+}
diff --git a/test/options/lines-between-rulesets/process/less/lines-between-rulesets.less b/test/options/lines-between-rulesets/process/less/lines-between-rulesets.less
new file mode 100644
index 00000000..544895b5
--- /dev/null
+++ b/test/options/lines-between-rulesets/process/less/lines-between-rulesets.less
@@ -0,0 +1,28 @@
+.foo {
+ .border-radius(5px);
+ background: red;
+ /* comment */
+ .omg {
+ .test {
+ height: 50px;
+ }
+ }
+}.bar {
+ border: 1px solid red;
+ /*
+ ** another comment
+ ** spanning multiple lines
+ */
+ @media (min-width: 500px) {
+ width: 50px;
+ }
+}.baz {
+ @grey: #ccc;
+ // a single-line comment
+ // another single-line comment
+ .wtf {
+ @media only screen {
+ background-color: @grey;
+ }
+ }
+}
diff --git a/test/options/lines-between-rulesets/process/sass/2-lines-between-rulesets.expected.sass b/test/options/lines-between-rulesets/process/sass/2-lines-between-rulesets.expected.sass
new file mode 100644
index 00000000..b35b9173
--- /dev/null
+++ b/test/options/lines-between-rulesets/process/sass/2-lines-between-rulesets.expected.sass
@@ -0,0 +1,32 @@
+.foo
+ +border-radius(5px)
+ background: red
+
+
+ // comment
+ .omg
+ .test
+ height: 50px
+
+
+.bar
+ border: 1px solid red
+
+
+ /*
+ * another comment
+ * spanning multiple lines
+ */
+ @media (min-width: 500px)
+ width: 50px
+
+
+.baz
+ $grey: #ccc
+
+
+ // a single-line comment
+ // another single-line comment
+ .wtf
+ @media only screen
+ background-color: $grey
diff --git a/test/options/lines-between-rulesets/process/sass/lines-between-rulesets.expected.sass b/test/options/lines-between-rulesets/process/sass/lines-between-rulesets.expected.sass
new file mode 100644
index 00000000..31a2a3d8
--- /dev/null
+++ b/test/options/lines-between-rulesets/process/sass/lines-between-rulesets.expected.sass
@@ -0,0 +1,27 @@
+.foo
+ +border-radius(5px)
+ background: red
+
+ // comment
+ .omg
+ .test
+ height: 50px
+
+.bar
+ border: 1px solid red
+
+ /*
+ * another comment
+ * spanning multiple lines
+ */
+ @media (min-width: 500px)
+ width: 50px
+
+.baz
+ $grey: #ccc
+
+ // a single-line comment
+ // another single-line comment
+ .wtf
+ @media only screen
+ background-color: $grey
diff --git a/test/options/lines-between-rulesets/process/sass/lines-between-rulesets.sass b/test/options/lines-between-rulesets/process/sass/lines-between-rulesets.sass
new file mode 100644
index 00000000..10ee7156
--- /dev/null
+++ b/test/options/lines-between-rulesets/process/sass/lines-between-rulesets.sass
@@ -0,0 +1,22 @@
+.foo
+ +border-radius(5px)
+ background: red
+ // comment
+ .omg
+ .test
+ height: 50px
+.bar
+ border: 1px solid red
+ /*
+ * another comment
+ * spanning multiple lines
+ */
+ @media (min-width: 500px)
+ width: 50px
+.baz
+ $grey: #ccc
+ // a single-line comment
+ // another single-line comment
+ .wtf
+ @media only screen
+ background-color: $grey
diff --git a/test/options/lines-between-rulesets/process/scss/2-lines-between-rulesets.expected.scss b/test/options/lines-between-rulesets/process/scss/2-lines-between-rulesets.expected.scss
new file mode 100644
index 00000000..386fbb18
--- /dev/null
+++ b/test/options/lines-between-rulesets/process/scss/2-lines-between-rulesets.expected.scss
@@ -0,0 +1,40 @@
+.foo {
+ @include border-radius(5px);
+ background: red;
+
+
+ /* comment */
+ .omg {
+ .test {
+ height: 50px;
+ }
+ }
+}
+
+
+.bar {
+ border: 1px solid red;
+
+
+ /*
+ ** another comment
+ ** spanning multiple lines
+ */
+ @media (min-width: 500px) {
+ width: 50px;
+ }
+}
+
+
+.baz {
+ $grey: #ccc;
+
+
+ // a single-line comment
+ // another single-line comment
+ .wtf {
+ @media only screen {
+ background-color: $grey;
+ }
+ }
+}
diff --git a/test/options/lines-between-rulesets/process/scss/lines-between-rulesets.expected.scss b/test/options/lines-between-rulesets/process/scss/lines-between-rulesets.expected.scss
new file mode 100644
index 00000000..db92922e
--- /dev/null
+++ b/test/options/lines-between-rulesets/process/scss/lines-between-rulesets.expected.scss
@@ -0,0 +1,35 @@
+.foo {
+ @include border-radius(5px);
+ background: red;
+
+ /* comment */
+ .omg {
+ .test {
+ height: 50px;
+ }
+ }
+}
+
+.bar {
+ border: 1px solid red;
+
+ /*
+ ** another comment
+ ** spanning multiple lines
+ */
+ @media (min-width: 500px) {
+ width: 50px;
+ }
+}
+
+.baz {
+ $grey: #ccc;
+
+ // a single-line comment
+ // another single-line comment
+ .wtf {
+ @media only screen {
+ background-color: $grey;
+ }
+ }
+}
diff --git a/test/options/lines-between-rulesets/process/scss/lines-between-rulesets.scss b/test/options/lines-between-rulesets/process/scss/lines-between-rulesets.scss
new file mode 100644
index 00000000..efa5b90f
--- /dev/null
+++ b/test/options/lines-between-rulesets/process/scss/lines-between-rulesets.scss
@@ -0,0 +1,28 @@
+.foo {
+ @include border-radius(5px);
+ background: red;
+ /* comment */
+ .omg {
+ .test {
+ height: 50px;
+ }
+ }
+}.bar {
+ border: 1px solid red;
+ /*
+ ** another comment
+ ** spanning multiple lines
+ */
+ @media (min-width: 500px) {
+ width: 50px;
+ }
+}.baz {
+ $grey: #ccc;
+ // a single-line comment
+ // another single-line comment
+ .wtf {
+ @media only screen {
+ background-color: $grey;
+ }
+ }
+}
diff --git a/test/options/lines-between-rulesets/process/test.js b/test/options/lines-between-rulesets/process/test.js
new file mode 100644
index 00000000..b6f935ca
--- /dev/null
+++ b/test/options/lines-between-rulesets/process/test.js
@@ -0,0 +1,93 @@
+'use strict';
+
+let Test = require('../../option_test');
+
+describe('Option `lines-between-rulesets`, process', function() {
+ describe('css', function() {
+ it('Numeric value => should insert 1 newline between rulesets', function() {
+ let test = new Test(this, { 'lines-between-rulesets': 1 });
+ return test.shouldBeEqual('lines-between-rulesets.css', 'lines-between-rulesets.expected.css');
+ });
+
+ it('Numeric value => should insert multiple newlines between rulesets', function() {
+ let test = new Test(this, { 'lines-between-rulesets': 2 });
+ return test.shouldBeEqual('lines-between-rulesets.css', '2-lines-between-rulesets.expected.css');
+ });
+
+ it('Invalid string value => should should not change anything', function() {
+ let test = new Test(this, { 'lines-between-rulesets': '\n' });
+ return test.shouldBeEqual('lines-between-rulesets.css');
+ });
+
+ it('Float number value => should be rounded', function() {
+ let test = new Test(this, { 'lines-between-rulesets': 0.5 });
+ return test.shouldBeEqual('lines-between-rulesets.css', 'lines-between-rulesets.expected.css');
+ });
+ });
+
+ describe('less', function() {
+ it('Numeric value => should insert 1 newline between rulesets', function() {
+ let test = new Test(this, { 'lines-between-rulesets': 1 });
+ return test.shouldBeEqual('lines-between-rulesets.less', 'lines-between-rulesets.expected.less');
+ });
+
+ it('Numeric value => should insert multiple newlines between rulesets', function() {
+ let test = new Test(this, { 'lines-between-rulesets': 2 });
+ return test.shouldBeEqual('lines-between-rulesets.less', '2-lines-between-rulesets.expected.less');
+ });
+
+ it('Invalid string value => should should not change anything', function() {
+ let test = new Test(this, { 'lines-between-rulesets': '\n' });
+ return test.shouldBeEqual('lines-between-rulesets.less');
+ });
+
+ it('Float number value => should be rounded', function() {
+ let test = new Test(this, { 'lines-between-rulesets': 0.5 });
+ return test.shouldBeEqual('lines-between-rulesets.less', 'lines-between-rulesets.expected.less');
+ });
+ });
+
+ describe('scss', function() {
+ it('Numeric value => should insert 1 newline between rulesets', function() {
+ let test = new Test(this, { 'lines-between-rulesets': 1 });
+ return test.shouldBeEqual('lines-between-rulesets.scss', 'lines-between-rulesets.expected.scss');
+ });
+
+ it('Numeric value => should insert multiple newlines between rulesets', function() {
+ let test = new Test(this, { 'lines-between-rulesets': 2 });
+ return test.shouldBeEqual('lines-between-rulesets.scss', '2-lines-between-rulesets.expected.scss');
+ });
+
+ it('Invalid string value => should should not change anything', function() {
+ let test = new Test(this, { 'lines-between-rulesets': '\n' });
+ return test.shouldBeEqual('lines-between-rulesets.scss');
+ });
+
+ it('Float number value => should be rounded', function() {
+ let test = new Test(this, { 'lines-between-rulesets': 0.5 });
+ return test.shouldBeEqual('lines-between-rulesets.scss', 'lines-between-rulesets.expected.scss');
+ });
+ });
+
+ describe('sass', function() {
+ it('Numeric value => should insert 1 newline between rulesets', function() {
+ let test = new Test(this, { 'lines-between-rulesets': 1 });
+ return test.shouldBeEqual('lines-between-rulesets.sass', 'lines-between-rulesets.expected.sass');
+ });
+
+ it('Numeric value => should insert multiple newlines between rulesets', function() {
+ let test = new Test(this, { 'lines-between-rulesets': 2 });
+ return test.shouldBeEqual('lines-between-rulesets.sass', '2-lines-between-rulesets.expected.sass');
+ });
+
+ it('Invalid string value => should should not change anything', function() {
+ let test = new Test(this, { 'lines-between-rulesets': '\n' });
+ return test.shouldBeEqual('lines-between-rulesets.sass');
+ });
+
+ it('Float number value => should be rounded', function() {
+ let test = new Test(this, { 'lines-between-rulesets': 0.5 });
+ return test.shouldBeEqual('lines-between-rulesets.sass', 'lines-between-rulesets.expected.sass');
+ });
+ });
+});
diff --git a/test/options/option_test.js b/test/options/option_test.js
new file mode 100644
index 00000000..e17e7e87
--- /dev/null
+++ b/test/options/option_test.js
@@ -0,0 +1,54 @@
+let assert = require('assert');
+let fs = require('fs');
+let path = require('path');
+
+let Comb = require('../../lib/csscomb');
+
+class OptionTest {
+ constructor(context, config) {
+ this.file = context.test.file;
+ this.syntax = context.test.parent.title;
+
+ this.Comb = Comb;
+ this.comb = new Comb();
+ if (config) this.comb.configure(config);
+ }
+
+ useConfig(name) {
+ let config = Comb.getConfig(name);
+ this.comb.configure(config);
+ }
+
+ getErrors(filename) {
+ let input = this.readFile(filename);
+ return this.comb.lintString(input, {syntax: this.syntax});
+ }
+
+ shouldBeEqual(inputFile, expectedFile) {
+ let input = this.readFile(inputFile);
+ let expected = expectedFile ? this.readFile(expectedFile) : input;
+
+ return this.comb.processString(input, {syntax: this.syntax})
+ .then(string => assert.equal(string, expected));
+ }
+
+ /**
+ * Detect options in a file and compare result with expected.
+ * File names should be relative to test suite's folder.
+ * @param {Array} options List of options that should be detected
+ * @param {String} input Name of template file
+ * @param {Object} expected Expected config with detected options
+ */
+ shouldDetect(options, input, expected) {
+ let detectedConfig = Comb.detectInString(input, options);
+ assert.deepEqual(detectedConfig, expected);
+ }
+
+ readFile(filename) {
+ let dirname = path.dirname(this.file);
+ let filePath = path.join(dirname, this.syntax, filename);
+ return fs.readFileSync(filePath, 'utf8');
+ }
+}
+
+module.exports = OptionTest;
diff --git a/test/options/quotes/detect/test.js b/test/options/quotes/detect/test.js
new file mode 100644
index 00000000..959a4abb
--- /dev/null
+++ b/test/options/quotes/detect/test.js
@@ -0,0 +1,51 @@
+let Test = require('../../option_test');
+
+describe('Option `quotes`, detect', function() {
+ describe('css', function() {
+ it('Should not detect quotes when there are none', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['quotes'],
+ 'a { color:red }',
+ {}
+ );
+ });
+
+ it('Should detect double quotes', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['quotes'],
+ 'a { content: "foo" }',
+ {quotes: 'double'}
+ );
+ });
+
+ it('Should detect single quotes', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['quotes'],
+ 'a { content: \'foo\' }',
+ {quotes: 'single'}
+ );
+ });
+
+ it('Should detect single quotes in attribute', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['quotes'],
+ 'a[class^=\'foo\'] { color: red }',
+ {quotes: 'single'}
+ );
+ });
+
+ it('Should detect double quotes in url', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['quotes'],
+ 'a { background: url("foo.png") }',
+ {quotes: 'double'}
+ );
+ });
+ });
+});
+
diff --git a/test/options/quotes/lint/test.js b/test/options/quotes/lint/test.js
new file mode 100644
index 00000000..e69de29b
diff --git a/test/options/quotes/process/test.js b/test/options/quotes/process/test.js
new file mode 100644
index 00000000..e37562e2
--- /dev/null
+++ b/test/options/quotes/process/test.js
@@ -0,0 +1,60 @@
+var assert = require('assert');
+let Test = require('../../option_test');
+
+describe('Option `quotes`, process', function() {
+ describe('css', function() {
+ it('Invalid String should not change quotes', function() {
+ let test = new Test(this, {quotes: 3});
+ return test.comb.processString(
+ 'a { content: "" }b { content: \'\' }'
+ ).then(function(actual) {
+ assert.equal(actual, 'a { content: "" }b { content: \'\' }');
+ });
+ });
+
+ it('`single` value should set the quotes to single', function() {
+ let test = new Test(this, {quotes: 'single'});
+ return test.comb.processString(
+ 'a { content: "" }b { content: \'\' }'
+ ).then(function(actual) {
+ assert.equal(actual, 'a { content: \'\' }b { content: \'\' }');
+ });
+ });
+
+ it('`double` value should set the quotes to double', function() {
+ let test = new Test(this, {quotes: 'double'});
+ return test.comb.processString(
+ 'a { content: "" }b { content: \'\' }'
+ ).then(function(actual) {
+ assert.equal(actual, 'a { content: "" }b { content: "" }');
+ });
+ });
+
+ it('`double` value should set the quotes to double in attrs and urls', function() {
+ let test = new Test(this, {quotes: 'double'});
+ return test.comb.processString(
+ 'a[class^=\'foo\'] { background: url(\'foo.png\') }'
+ ).then(function(actual) {
+ assert.equal(actual, 'a[class^="foo"] { background: url("foo.png") }');
+ });
+ });
+
+ it('`double` value should escape the unescaped double quotes on change', function() {
+ let test = new Test(this, {quotes: 'double'});
+ return test.comb.processString(
+ 'a { content: "\\"" }b { content: \'"\' }'
+ ).then(function(actual) {
+ assert.equal(actual, 'a { content: "\\"" }b { content: "\\"" }');
+ });
+ });
+
+ it('`single` value should unescape the escaped double quotes on change', function() {
+ let test = new Test(this, {quotes: 'single'});
+ return test.comb.processString(
+ 'a { content: "\\"" }'
+ ).then(function(actual) {
+ assert.equal(actual, 'a { content: \'"\' }');
+ });
+ });
+ });
+});
diff --git a/test/options/remove-empty-rulesets/detect/test.js b/test/options/remove-empty-rulesets/detect/test.js
new file mode 100644
index 00000000..2fa734f5
--- /dev/null
+++ b/test/options/remove-empty-rulesets/detect/test.js
@@ -0,0 +1,78 @@
+let Test = require('../../option_test');
+
+describe('Option `remove-empty-rulesets`, detect', function() {
+ describe('css', function() {
+ it('Should detect test option set to `true`', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['remove-empty-rulesets'],
+ 'a { color: red }',
+ {'remove-empty-rulesets': true}
+ );
+ });
+
+ it('Should detect test option set to `false` with empty block', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['remove-empty-rulesets'],
+ 'a {}',
+ {'remove-empty-rulesets': false}
+ );
+ });
+
+ it('Should detect test option set to `false` with block containing whitespace', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['remove-empty-rulesets'],
+ 'a { }',
+ {'remove-empty-rulesets': false}
+ );
+ });
+
+ it('Should detect test option set to `true` with block containing comment', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['remove-empty-rulesets'],
+ 'a { /* Hello */ }',
+ {'remove-empty-rulesets': true}
+ );
+ });
+
+ it('Should detect test option set to `true` with media query containing block', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['remove-empty-rulesets'],
+ '@media all and (min-width:0) { a { /* Hello */ } }',
+ {'remove-empty-rulesets': true}
+ );
+ });
+
+ it('Should detect test option set to `true` with media query containing comment', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['remove-empty-rulesets'],
+ '@media all and (min-width:0) {/* Hello */}',
+ {'remove-empty-rulesets': true}
+ );
+ });
+
+ it('Should detect test option set to `false` with empty media query', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['remove-empty-rulesets'],
+ '@media all and (min-width:0) {}',
+ {'remove-empty-rulesets': false}
+ );
+ });
+
+ it('Should detect test option set to `false` with media query containing whitespace', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['remove-empty-rulesets'],
+ '@media all and (min-width:0) { \n }',
+ {'remove-empty-rulesets': false}
+ );
+ });
+ });
+});
+
diff --git a/test/options/remove-empty-rulesets/lint/test.js b/test/options/remove-empty-rulesets/lint/test.js
new file mode 100644
index 00000000..e69de29b
diff --git a/test/options/remove-empty-rulesets/process/less/1.expected.less b/test/options/remove-empty-rulesets/process/less/1.expected.less
new file mode 100644
index 00000000..fbb76512
--- /dev/null
+++ b/test/options/remove-empty-rulesets/process/less/1.expected.less
@@ -0,0 +1,14 @@
+#a {
+
+ #b {
+ text-align: right;
+ }
+
+
+
+ #d {
+ .e {
+ text-align: center;
+ }
+ }
+}
diff --git a/test/options/remove-empty-rulesets/process/less/1.less b/test/options/remove-empty-rulesets/process/less/1.less
new file mode 100644
index 00000000..6abc2477
--- /dev/null
+++ b/test/options/remove-empty-rulesets/process/less/1.less
@@ -0,0 +1,14 @@
+#a {
+
+ #b {
+ text-align: right;
+ }
+
+ #c { }
+
+ #d {
+ .e {
+ text-align: center;
+ }
+ }
+}
diff --git a/test/options/remove-empty-rulesets/process/scss/empty-nested-rule.expected.scss b/test/options/remove-empty-rulesets/process/scss/empty-nested-rule.expected.scss
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/test/options/remove-empty-rulesets/process/scss/empty-nested-rule.expected.scss
@@ -0,0 +1 @@
+
diff --git a/test/options/remove-empty-rulesets/process/scss/empty-nested-rule.scss b/test/options/remove-empty-rulesets/process/scss/empty-nested-rule.scss
new file mode 100644
index 00000000..89b0d979
--- /dev/null
+++ b/test/options/remove-empty-rulesets/process/scss/empty-nested-rule.scss
@@ -0,0 +1,5 @@
+.parent {
+ .child {
+ .grandchild { }
+ }
+}
diff --git a/test/options/remove-empty-rulesets/process/scss/include.scss b/test/options/remove-empty-rulesets/process/scss/include.scss
new file mode 100644
index 00000000..70d39a59
--- /dev/null
+++ b/test/options/remove-empty-rulesets/process/scss/include.scss
@@ -0,0 +1,4 @@
+.parent {
+ @include mix-all;
+ @include mix-top;
+}
diff --git a/test/options/remove-empty-rulesets/process/scss/nested-rule-1.scss b/test/options/remove-empty-rulesets/process/scss/nested-rule-1.scss
new file mode 100644
index 00000000..f69f70a2
--- /dev/null
+++ b/test/options/remove-empty-rulesets/process/scss/nested-rule-1.scss
@@ -0,0 +1,6 @@
+.parent {
+ .child {
+ @include mix-all;
+ @include mix-top;
+ }
+}
diff --git a/test/options/remove-empty-rulesets/process/scss/nested-rule-2.expected.scss b/test/options/remove-empty-rulesets/process/scss/nested-rule-2.expected.scss
new file mode 100644
index 00000000..b020e30f
--- /dev/null
+++ b/test/options/remove-empty-rulesets/process/scss/nested-rule-2.expected.scss
@@ -0,0 +1,7 @@
+.parent {
+ .child1 {
+ @include mix-all;
+ @include mix-top;
+ }
+
+}
diff --git a/test/options/remove-empty-rulesets/process/scss/nested-rule-2.scss b/test/options/remove-empty-rulesets/process/scss/nested-rule-2.scss
new file mode 100644
index 00000000..f5eedd44
--- /dev/null
+++ b/test/options/remove-empty-rulesets/process/scss/nested-rule-2.scss
@@ -0,0 +1,9 @@
+.parent {
+ .child1 {
+ @include mix-all;
+ @include mix-top;
+ }
+ .child2 {
+ .grandchild { }
+ }
+}
diff --git a/test/options/remove-empty-rulesets/process/test.js b/test/options/remove-empty-rulesets/process/test.js
new file mode 100644
index 00000000..c80097b5
--- /dev/null
+++ b/test/options/remove-empty-rulesets/process/test.js
@@ -0,0 +1,96 @@
+var assert = require('assert');
+let Test = require('../../option_test');
+
+describe('Option `remove-empty-rulesets`, process', function() {
+ describe('css', function() {
+ it('Configured with invalid value, should not remove empty ruleset', function() {
+ let test = new Test(this, {'remove-empty-rulesets': 'foobar'});
+ return test.comb.processString('a { width: 10px; } b {}')
+ .then(function(actual) {
+ assert.equal(actual, 'a { width: 10px; } b {}');
+ });
+ });
+
+ it('Should remove empty ruleset', function() {
+ let test = new Test(this, {'remove-empty-rulesets': true});
+ return test.comb.processString(' b {} ')
+ .then(function(actual) {
+ assert.equal(actual, ' ');
+ });
+ });
+
+ it('Should remove ruleset with spaces', function() {
+ let test = new Test(this, {'remove-empty-rulesets': true});
+ return test.comb.processString(' b { } ')
+ .then(function(actual) {
+ assert.equal(actual, ' ');
+ });
+ });
+
+ it('Should leave ruleset with declarations', function() {
+ let test = new Test(this, {'remove-empty-rulesets': true});
+ return test.comb.processString('a { width: 10px; }\nb {} ')
+ .then(function(actual) {
+ assert.equal(actual, 'a { width: 10px; }\n ');
+ });
+ });
+
+ it('Should leave ruleset with comments', function() {
+ let test = new Test(this, {'remove-empty-rulesets': true});
+ return test.comb.processString('a { /* comment */ }\nb {} ')
+ .then(function(actual) {
+ assert.equal(actual, 'a { /* comment */ }\n ');
+ });
+ });
+ });
+
+ describe('less', function() {
+ it('Issue 201. Test 1', function() {
+ let test = new Test(this, {'remove-empty-rulesets': true});
+ return test.shouldBeEqual('1.less', '1.expected.less');
+ });
+
+ it('Issue 201. Test 2', function() {
+ let test = new Test(this, {'remove-empty-rulesets': true});
+ let string = '#a {#b {} #d {}}';
+ return test.comb.processString(string, {syntax: 'less'})
+ .then(function(actual) {
+ assert.equal(actual, '');
+ });
+ });
+
+ it('Issue 201. Test 3', function() {
+ let test = new Test(this, {
+ 'remove-empty-rulesets': false,
+ 'always-semicolon': true
+ });
+ let string = '#a {#b {} #d {}}';
+ return test.comb.processString(string, {syntax: 'less'})
+ .then(function(actual) {
+ assert.equal(actual, string);
+ });
+ });
+ });
+
+ describe('scss', function() {
+ it('Should not remove rulesets which contain only includes', function() {
+ let test = new Test(this, {'remove-empty-rulesets': true});
+ return test.shouldBeEqual('include.scss');
+ });
+
+ it('Should remove rulesets with contain only empty nested rules', function() {
+ let test = new Test(this, {'remove-empty-rulesets': true});
+ return test.shouldBeEqual('empty-nested-rule.scss', 'empty-nested-rule.expected.scss');
+ });
+
+ it('Should not remove rulesets with non-empty nested rules. Test 1', function() {
+ let test = new Test(this, {'remove-empty-rulesets': true});
+ return test.shouldBeEqual('nested-rule-1.scss');
+ });
+
+ it('Should not remove rulesets with non-empty nested rules. Test 2', function() {
+ let test = new Test(this, {'remove-empty-rulesets': true});
+ return test.shouldBeEqual('nested-rule-2.scss', 'nested-rule-2.expected.scss');
+ });
+ });
+});
diff --git a/test/options/sort-order-fallback/detect/test.js b/test/options/sort-order-fallback/detect/test.js
new file mode 100644
index 00000000..e69de29b
diff --git a/test/options/sort-order-fallback/lint/test.js b/test/options/sort-order-fallback/lint/test.js
new file mode 100644
index 00000000..e69de29b
diff --git a/test/options/sort-order-fallback/process/css/same-property.css b/test/options/sort-order-fallback/process/css/same-property.css
new file mode 100644
index 00000000..8af2a139
--- /dev/null
+++ b/test/options/sort-order-fallback/process/css/same-property.css
@@ -0,0 +1,4 @@
+button {
+ outline: 1px dotted;
+ outline: 5px auto -webkit-focus-ring-color;
+}
diff --git a/test/options/sort-order-fallback/process/css/test-2.expected.css b/test/options/sort-order-fallback/process/css/test-2.expected.css
new file mode 100644
index 00000000..dd35868e
--- /dev/null
+++ b/test/options/sort-order-fallback/process/css/test-2.expected.css
@@ -0,0 +1,11 @@
+a {
+ top: 0;
+ left: 0;
+
+ -moz-color: zebra;
+ color: tomato;
+ -webkit-float: left;
+ float: right;
+ font-size: 1em;
+ position: absolute;
+ }
diff --git a/test/options/sort-order-fallback/process/css/test-3.expected.css b/test/options/sort-order-fallback/process/css/test-3.expected.css
new file mode 100644
index 00000000..8e312988
--- /dev/null
+++ b/test/options/sort-order-fallback/process/css/test-3.expected.css
@@ -0,0 +1,11 @@
+a {
+ top: 0;
+ left: 0;
+
+ color: tomato;
+ font-size: 1em;
+ position: absolute;
+ -webkit-float: left;
+ -moz-color: zebra;
+ float: right;
+ }
diff --git a/test/options/sort-order-fallback/process/css/test.css b/test/options/sort-order-fallback/process/css/test.css
new file mode 100644
index 00000000..d57de84a
--- /dev/null
+++ b/test/options/sort-order-fallback/process/css/test.css
@@ -0,0 +1,10 @@
+a {
+ color: tomato;
+ font-size: 1em;
+ top: 0;
+ position: absolute;
+ left: 0;
+ -webkit-float: left;
+ -moz-color: zebra;
+ float: right;
+ }
diff --git a/test/options/sort-order-fallback/process/css/test.expected.css b/test/options/sort-order-fallback/process/css/test.expected.css
new file mode 100644
index 00000000..ec1c2a8f
--- /dev/null
+++ b/test/options/sort-order-fallback/process/css/test.expected.css
@@ -0,0 +1,12 @@
+a {
+ top: 0;
+ left: 0;
+
+ -moz-color: zebra;
+ -webkit-float: left;
+ float: right;
+ font-size: 1em;
+ position: absolute;
+
+ color: tomato;
+ }
diff --git a/test/options/sort-order-fallback/process/test.js b/test/options/sort-order-fallback/process/test.js
new file mode 100644
index 00000000..9127d943
--- /dev/null
+++ b/test/options/sort-order-fallback/process/test.js
@@ -0,0 +1,44 @@
+let Test = require('../../option_test');
+
+describe('Option `sort-order-fallback`, process', function() {
+ describe('css', function() {
+ it('Should sort leftovers alphabetically if `sort-order-fallback` is set', function() {
+ let config = {
+ 'sort-order-fallback': 'abc',
+ 'sort-order': [
+ ['top', 'left'],
+ ['...'],
+ ['color']
+ ]
+ };
+ let test = new Test(this, config);
+ return test.shouldBeEqual('test.css', 'test.expected.css');
+ });
+
+ it('Should sort unknown properties alphabetically if `sort-order-fallback` is set', function() {
+ let config = {
+ 'sort-order-fallback': 'abc',
+ 'sort-order': ['top', 'left']
+ };
+ let test = new Test(this, config);
+ return test.shouldBeEqual('test.css', 'test-2.expected.css');
+ });
+
+ it('Properties of the same name should stay in the same order', function() {
+ let config = {
+ 'sort-order': ['...'],
+ 'sort-order-fallback': 'abc'
+ };
+ let test = new Test(this, config);
+ return test.shouldBeEqual('same-property.css');
+ });
+
+ it('Should leave leftovers as is if `sort-order-fallback` is not set', function() {
+ let config = {
+ 'sort-order': ['top', 'left']
+ };
+ let test = new Test(this, config);
+ return test.shouldBeEqual('test.css', 'test-3.expected.css');
+ });
+ });
+});
diff --git a/test/options/sort-order/detect/test.js b/test/options/sort-order/detect/test.js
new file mode 100644
index 00000000..e69de29b
diff --git a/test/options/sort-order/lint/test.js b/test/options/sort-order/lint/test.js
new file mode 100644
index 00000000..e69de29b
diff --git a/test/options/sort-order/process/css/data-uri.css b/test/options/sort-order/process/css/data-uri.css
new file mode 100644
index 00000000..3552dfe5
--- /dev/null
+++ b/test/options/sort-order/process/css/data-uri.css
@@ -0,0 +1,7 @@
+a
+{
+ color: tomato;
+ background: #D2D2D2 no-repeat url('data:image/svg+xml;charset=US-ASCII.naninani');
+ position: absolute;
+}
+
diff --git a/test/options/sort-order/process/css/data-uri.expected.css b/test/options/sort-order/process/css/data-uri.expected.css
new file mode 100644
index 00000000..74b14a23
--- /dev/null
+++ b/test/options/sort-order/process/css/data-uri.expected.css
@@ -0,0 +1,7 @@
+a
+{
+ position: absolute;
+ background: #D2D2D2 no-repeat url('data:image/svg+xml;charset=US-ASCII.naninani');
+ color: tomato;
+}
+
diff --git a/test/options/sort-order/process/css/issue-94-1.css b/test/options/sort-order/process/css/issue-94-1.css
new file mode 100644
index 00000000..54131c51
--- /dev/null
+++ b/test/options/sort-order/process/css/issue-94-1.css
@@ -0,0 +1,18 @@
+.test
+{
+ position: absolute;
+ top: -1px;
+ right: -1px;
+ bottom: -1px;
+ left: -1px;
+
+ border: 1px solid transparent;
+ border-color: rgba(0,0,0,0.38) rgba(0,0,0,0.27) rgba(0,0,0,0.16);
+
+ background: -webkit-linear-gradient(#fff, #fff);
+ background: linear-gradient(#fff, #fff);
+ background-clip: padding-box;
+ background-size: 16px 16px;
+
+ -webkit-appearance: textfield;
+}
diff --git a/test/options/sort-order/process/css/issue-94-1.expected.css b/test/options/sort-order/process/css/issue-94-1.expected.css
new file mode 100644
index 00000000..0943808b
--- /dev/null
+++ b/test/options/sort-order/process/css/issue-94-1.expected.css
@@ -0,0 +1,17 @@
+.test
+{
+ position: absolute;
+ top: -1px;
+ right: -1px;
+ bottom: -1px;
+ left: -1px;
+
+ border: 1px solid transparent;
+ border-color: rgba(0,0,0,.38) rgba(0,0,0,.27) rgba(0,0,0,.16);
+ background: -webkit-linear-gradient(#fff, #fff);
+ background: linear-gradient(#fff, #fff);
+ background-clip: padding-box;
+ background-size: 16px 16px;
+
+ -webkit-appearance: textfield;
+}
diff --git a/test/options/sort-order/process/css/issue-94-2.css b/test/options/sort-order/process/css/issue-94-2.css
new file mode 100644
index 00000000..0365913c
--- /dev/null
+++ b/test/options/sort-order/process/css/issue-94-2.css
@@ -0,0 +1,19 @@
+.test
+{
+ width: 0;
+ width: 100%;
+ height: 0;
+
+ background: #fff;
+ color: #000;
+
+ display: -moz-inline-stack;
+ display: inline-block;
+
+ position: absolute;
+ position: fixed;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+}
diff --git a/test/options/sort-order/process/css/issue-94-2.expected.css b/test/options/sort-order/process/css/issue-94-2.expected.css
new file mode 100644
index 00000000..85e618de
--- /dev/null
+++ b/test/options/sort-order/process/css/issue-94-2.expected.css
@@ -0,0 +1,19 @@
+.test
+{
+ position: absolute;
+ position: fixed;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+
+ display: -moz-inline-stack;
+ display: inline-block;
+
+ width: 0;
+ width: 100%;
+ height: 0;
+
+ color: #000;
+ background: #fff;
+}
diff --git a/test/options/sort-order/process/css/issue-94-3.css b/test/options/sort-order/process/css/issue-94-3.css
new file mode 100644
index 00000000..872eddcf
--- /dev/null
+++ b/test/options/sort-order/process/css/issue-94-3.css
@@ -0,0 +1,21 @@
+.input-view {
+ position: absolute;
+ -webkit-appearance: none;
+ right: -1px;
+ bottom: -1px;
+ left: -1px;
+
+ border: 1px solid transparent;
+
+ top: -1px;
+
+ background: -webkit-linear-gradient(#FFF, #FFF);
+ background: linear-gradient(#FFF, #FFF);
+ background-clip: padding-box;
+ background-size: 16px 16px;
+ box-shadow: 0 1px 0 rgba(255,255,255,0.2), inset 0 1px 1px rgba(0,0,0,0.1);
+
+ border-color: rgba(0,0,0,0.27);
+ border-top-color: rgba(0,0,0,0.38);
+ border-bottom-color: rgba(0,0,0,0.16);
+}
diff --git a/test/options/sort-order/process/css/issue-94-3.expected.css b/test/options/sort-order/process/css/issue-94-3.expected.css
new file mode 100644
index 00000000..92f29432
--- /dev/null
+++ b/test/options/sort-order/process/css/issue-94-3.expected.css
@@ -0,0 +1,20 @@
+.input-view
+{
+ position: absolute;
+ top: -1px;
+ right: -1px;
+ bottom: -1px;
+ left: -1px;
+
+ border: 1px solid transparent;
+ border-color: rgba(0,0,0,.27);
+ border-top-color: rgba(0,0,0,.38);
+ border-bottom-color: rgba(0,0,0,.16);
+ background: -webkit-linear-gradient(#fff, #fff);
+ background: linear-gradient(#fff, #fff);
+ background-clip: padding-box;
+ background-size: 16px 16px;
+ box-shadow: 0 1px 0 rgba(255,255,255,.2), inset 0 1px 1px rgba(0,0,0,.1);
+
+ -webkit-appearance: none;
+}
diff --git a/test/options/sort-order/process/css/leftovers-1.css b/test/options/sort-order/process/css/leftovers-1.css
new file mode 100644
index 00000000..d559d4e3
--- /dev/null
+++ b/test/options/sort-order/process/css/leftovers-1.css
@@ -0,0 +1,20 @@
+a
+{
+ leftover: yay;
+ z-index: 1;
+ position: absolute;
+}
+
+b
+{
+ z-index: 2;
+ leftover: yay;
+ position: absolute;
+}
+
+c
+{
+ z-index: 3;
+ position: absolute;
+ leftover: yay;
+}
diff --git a/test/options/sort-order/process/css/leftovers-1.expected.css b/test/options/sort-order/process/css/leftovers-1.expected.css
new file mode 100644
index 00000000..145b83db
--- /dev/null
+++ b/test/options/sort-order/process/css/leftovers-1.expected.css
@@ -0,0 +1,23 @@
+a
+{
+ position: absolute;
+ z-index: 1;
+
+ leftover: yay;
+}
+
+b
+{
+ position: absolute;
+ z-index: 2;
+
+ leftover: yay;
+}
+
+c
+{
+ position: absolute;
+ z-index: 3;
+
+ leftover: yay;
+}
diff --git a/test/options/sort-order/process/css/leftovers-2.css b/test/options/sort-order/process/css/leftovers-2.css
new file mode 100644
index 00000000..d559d4e3
--- /dev/null
+++ b/test/options/sort-order/process/css/leftovers-2.css
@@ -0,0 +1,20 @@
+a
+{
+ leftover: yay;
+ z-index: 1;
+ position: absolute;
+}
+
+b
+{
+ z-index: 2;
+ leftover: yay;
+ position: absolute;
+}
+
+c
+{
+ z-index: 3;
+ position: absolute;
+ leftover: yay;
+}
diff --git a/test/options/sort-order/process/css/leftovers-2.expected.css b/test/options/sort-order/process/css/leftovers-2.expected.css
new file mode 100644
index 00000000..05c82928
--- /dev/null
+++ b/test/options/sort-order/process/css/leftovers-2.expected.css
@@ -0,0 +1,23 @@
+a
+{
+ leftover: yay;
+
+ position: absolute;
+ z-index: 1;
+}
+
+b
+{
+ leftover: yay;
+
+ position: absolute;
+ z-index: 2;
+}
+
+c
+{
+ leftover: yay;
+
+ position: absolute;
+ z-index: 3;
+}
diff --git a/test/options/sort-order/process/css/leftovers-3.css b/test/options/sort-order/process/css/leftovers-3.css
new file mode 100644
index 00000000..d559d4e3
--- /dev/null
+++ b/test/options/sort-order/process/css/leftovers-3.css
@@ -0,0 +1,20 @@
+a
+{
+ leftover: yay;
+ z-index: 1;
+ position: absolute;
+}
+
+b
+{
+ z-index: 2;
+ leftover: yay;
+ position: absolute;
+}
+
+c
+{
+ z-index: 3;
+ position: absolute;
+ leftover: yay;
+}
diff --git a/test/options/sort-order/process/css/leftovers-3.expected.css b/test/options/sort-order/process/css/leftovers-3.expected.css
new file mode 100644
index 00000000..9522dc50
--- /dev/null
+++ b/test/options/sort-order/process/css/leftovers-3.expected.css
@@ -0,0 +1,20 @@
+a
+{
+ leftover: yay;
+ position: absolute;
+ z-index: 1;
+}
+
+b
+{
+ leftover: yay;
+ position: absolute;
+ z-index: 2;
+}
+
+c
+{
+ leftover: yay;
+ position: absolute;
+ z-index: 3;
+}
diff --git a/test/options/sort-order/process/css/leftovers-4.css b/test/options/sort-order/process/css/leftovers-4.css
new file mode 100644
index 00000000..d559d4e3
--- /dev/null
+++ b/test/options/sort-order/process/css/leftovers-4.css
@@ -0,0 +1,20 @@
+a
+{
+ leftover: yay;
+ z-index: 1;
+ position: absolute;
+}
+
+b
+{
+ z-index: 2;
+ leftover: yay;
+ position: absolute;
+}
+
+c
+{
+ z-index: 3;
+ position: absolute;
+ leftover: yay;
+}
diff --git a/test/options/sort-order/process/css/leftovers-4.expected.css b/test/options/sort-order/process/css/leftovers-4.expected.css
new file mode 100644
index 00000000..874c72a4
--- /dev/null
+++ b/test/options/sort-order/process/css/leftovers-4.expected.css
@@ -0,0 +1,20 @@
+a
+{
+ position: absolute;
+ leftover: yay;
+ z-index: 1;
+}
+
+b
+{
+ position: absolute;
+ leftover: yay;
+ z-index: 2;
+}
+
+c
+{
+ position: absolute;
+ leftover: yay;
+ z-index: 3;
+}
diff --git a/test/options/sort-order/process/css/missing-delimiter.css b/test/options/sort-order/process/css/missing-delimiter.css
new file mode 100644
index 00000000..cdba2d46
--- /dev/null
+++ b/test/options/sort-order/process/css/missing-delimiter.css
@@ -0,0 +1,4 @@
+a {
+ z-index: 2;
+ position: absolute
+}
diff --git a/test/options/sort-order/process/css/missing-delimiter.expected.css b/test/options/sort-order/process/css/missing-delimiter.expected.css
new file mode 100644
index 00000000..f619ac51
--- /dev/null
+++ b/test/options/sort-order/process/css/missing-delimiter.expected.css
@@ -0,0 +1,4 @@
+a {
+ position: absolute;
+ z-index: 2;
+}
diff --git a/test/options/sort-order/process/css/multiple-groups-2.css b/test/options/sort-order/process/css/multiple-groups-2.css
new file mode 100644
index 00000000..79291aba
--- /dev/null
+++ b/test/options/sort-order/process/css/multiple-groups-2.css
@@ -0,0 +1,6 @@
+a
+{
+ color: tomato;
+ top: 0;
+}
+
diff --git a/test/options/sort-order/process/css/multiple-groups-2.expected.css b/test/options/sort-order/process/css/multiple-groups-2.expected.css
new file mode 100644
index 00000000..96a0ec2e
--- /dev/null
+++ b/test/options/sort-order/process/css/multiple-groups-2.expected.css
@@ -0,0 +1,7 @@
+a
+{
+ top: 0;
+
+ color: tomato;
+}
+
diff --git a/test/options/sort-order/process/css/multiple-groups-comments.css b/test/options/sort-order/process/css/multiple-groups-comments.css
new file mode 100644
index 00000000..5012f20f
--- /dev/null
+++ b/test/options/sort-order/process/css/multiple-groups-comments.css
@@ -0,0 +1,5 @@
+a, b, i /* foobar */
+{
+ padding: 0;
+ margin: 0;
+ }
diff --git a/test/options/sort-order/process/css/multiple-groups-comments.expected.css b/test/options/sort-order/process/css/multiple-groups-comments.expected.css
new file mode 100644
index 00000000..49a1ba88
--- /dev/null
+++ b/test/options/sort-order/process/css/multiple-groups-comments.expected.css
@@ -0,0 +1,6 @@
+a, b, i /* foobar */
+{
+ margin: 0;
+
+ padding: 0;
+ }
diff --git a/test/options/sort-order/process/css/multiple-groups.css b/test/options/sort-order/process/css/multiple-groups.css
new file mode 100644
index 00000000..a16d45e7
--- /dev/null
+++ b/test/options/sort-order/process/css/multiple-groups.css
@@ -0,0 +1,7 @@
+a
+{
+ z-index: 2;
+ position: absolute;
+ height: 2px;
+ width: 2px;
+}
diff --git a/test/options/sort-order/process/css/multiple-groups.expected.css b/test/options/sort-order/process/css/multiple-groups.expected.css
new file mode 100644
index 00000000..3f27b458
--- /dev/null
+++ b/test/options/sort-order/process/css/multiple-groups.expected.css
@@ -0,0 +1,8 @@
+a
+{
+ position: absolute;
+ z-index: 2;
+
+ width: 2px;
+ height: 2px;
+}
diff --git a/test/options/sort-order/process/css/single-group-comments.css b/test/options/sort-order/process/css/single-group-comments.css
new file mode 100644
index 00000000..dc78ba3b
--- /dev/null
+++ b/test/options/sort-order/process/css/single-group-comments.css
@@ -0,0 +1,5 @@
+div p em {
+ /* upline comment */
+ font-style:italic;
+ border-bottom:1px solid red; /* trololo */ /* trololo */
+}
diff --git a/test/options/sort-order/process/css/single-group-comments.expected.css b/test/options/sort-order/process/css/single-group-comments.expected.css
new file mode 100644
index 00000000..ca4e1665
--- /dev/null
+++ b/test/options/sort-order/process/css/single-group-comments.expected.css
@@ -0,0 +1,5 @@
+div p em {
+ border-bottom:1px solid red; /* trololo */ /* trololo */
+ /* upline comment */
+ font-style:italic;
+}
diff --git a/test/options/sort-order/process/css/single-group.css b/test/options/sort-order/process/css/single-group.css
new file mode 100644
index 00000000..ed8925d0
--- /dev/null
+++ b/test/options/sort-order/process/css/single-group.css
@@ -0,0 +1,5 @@
+a
+{
+ z-index: 2;
+ position: absolute;
+}
diff --git a/test/options/sort-order/process/css/single-group.expected.css b/test/options/sort-order/process/css/single-group.expected.css
new file mode 100644
index 00000000..5bcb46c2
--- /dev/null
+++ b/test/options/sort-order/process/css/single-group.expected.css
@@ -0,0 +1,5 @@
+a
+{
+ position: absolute;
+ z-index: 2;
+}
diff --git a/test/options/sort-order/process/less/comments-1.expected.less b/test/options/sort-order/process/less/comments-1.expected.less
new file mode 100644
index 00000000..a9b2976c
--- /dev/null
+++ b/test/options/sort-order/process/less/comments-1.expected.less
@@ -0,0 +1 @@
+div {top: 0; /* 3 */ /* 4 *//* 1 */ color: tomato; /* 2 */ }
diff --git a/test/options/sort-order/process/less/comments-1.less b/test/options/sort-order/process/less/comments-1.less
new file mode 100644
index 00000000..617d4fdc
--- /dev/null
+++ b/test/options/sort-order/process/less/comments-1.less
@@ -0,0 +1 @@
+div {/* 1 */ color: tomato; /* 2 */ top: 0; /* 3 */ /* 4 */}
diff --git a/test/options/sort-order/process/less/comments-2.expected.less b/test/options/sort-order/process/less/comments-2.expected.less
new file mode 100644
index 00000000..0aa257a7
--- /dev/null
+++ b/test/options/sort-order/process/less/comments-2.expected.less
@@ -0,0 +1,7 @@
+div {
+ /* 2 */
+ /* 3 */
+ top: 0; /* 4 */
+ color: tomato; /* 1 */
+ /* 5 */
+}
diff --git a/test/options/sort-order/process/less/comments-2.less b/test/options/sort-order/process/less/comments-2.less
new file mode 100644
index 00000000..18e6319b
--- /dev/null
+++ b/test/options/sort-order/process/less/comments-2.less
@@ -0,0 +1,7 @@
+div {
+ color: tomato; /* 1 */
+ /* 2 */
+ /* 3 */
+ top: 0; /* 4 */
+ /* 5 */
+}
diff --git a/test/options/sort-order/process/less/comments-3.expected.less b/test/options/sort-order/process/less/comments-3.expected.less
new file mode 100644
index 00000000..7bf0938d
--- /dev/null
+++ b/test/options/sort-order/process/less/comments-3.expected.less
@@ -0,0 +1,6 @@
+p {
+ // Get in line!
+ @var: white;
+ /* One hell of a comment */
+ color: tomato;
+}
diff --git a/test/options/sort-order/process/less/comments-3.less b/test/options/sort-order/process/less/comments-3.less
new file mode 100644
index 00000000..c7f0a84f
--- /dev/null
+++ b/test/options/sort-order/process/less/comments-3.less
@@ -0,0 +1,6 @@
+p {
+ /* One hell of a comment */
+ color: tomato;
+ // Get in line!
+ @var: white;
+}
diff --git a/test/options/sort-order/process/less/comments-4.expected.less b/test/options/sort-order/process/less/comments-4.expected.less
new file mode 100644
index 00000000..349b7702
--- /dev/null
+++ b/test/options/sort-order/process/less/comments-4.expected.less
@@ -0,0 +1,4 @@
+p {
+ @var: white; // Get in line!
+ color: tomato; /* One hell of a comment */
+ }
diff --git a/test/options/sort-order/process/less/comments-4.less b/test/options/sort-order/process/less/comments-4.less
new file mode 100644
index 00000000..b1db838f
--- /dev/null
+++ b/test/options/sort-order/process/less/comments-4.less
@@ -0,0 +1,4 @@
+p {
+ color: tomato; /* One hell of a comment */
+ @var: white; // Get in line!
+ }
diff --git a/test/options/sort-order/process/less/different-groups.expected.less b/test/options/sort-order/process/less/different-groups.expected.less
new file mode 100644
index 00000000..dddc16f8
--- /dev/null
+++ b/test/options/sort-order/process/less/different-groups.expected.less
@@ -0,0 +1,5 @@
+div {
+ top: 0;
+
+ color: tomato;
+}
diff --git a/test/options/sort-order/process/less/different-groups.less b/test/options/sort-order/process/less/different-groups.less
new file mode 100644
index 00000000..7ffa908a
--- /dev/null
+++ b/test/options/sort-order/process/less/different-groups.less
@@ -0,0 +1,4 @@
+div {
+ color: tomato;
+ top: 0;
+}
diff --git a/test/options/sort-order/process/less/extend.expected.less b/test/options/sort-order/process/less/extend.expected.less
new file mode 100644
index 00000000..82f0e172
--- /dev/null
+++ b/test/options/sort-order/process/less/extend.expected.less
@@ -0,0 +1,7 @@
+.block {
+ &:extend(.foo);
+ color: black;
+}
+.a:extend(.b) {
+ color: pink;
+}
diff --git a/test/options/sort-order/process/less/extend.less b/test/options/sort-order/process/less/extend.less
new file mode 100644
index 00000000..2d1fcff9
--- /dev/null
+++ b/test/options/sort-order/process/less/extend.less
@@ -0,0 +1,7 @@
+.block {
+ color: black;
+ &:extend(.foo);
+}
+.a:extend(.b) {
+ color: pink;
+}
diff --git a/test/options/sort-order/process/less/import.expected.less b/test/options/sort-order/process/less/import.expected.less
new file mode 100644
index 00000000..9c403dfe
--- /dev/null
+++ b/test/options/sort-order/process/less/import.expected.less
@@ -0,0 +1 @@
+div {@import "foo.css"; color: tomato; }
diff --git a/test/options/sort-order/process/less/import.less b/test/options/sort-order/process/less/import.less
new file mode 100644
index 00000000..681ccd06
--- /dev/null
+++ b/test/options/sort-order/process/less/import.less
@@ -0,0 +1 @@
+div { color: tomato; @import "foo.css"; }
diff --git a/test/options/sort-order/process/less/mixin-1.expected.less b/test/options/sort-order/process/less/mixin-1.expected.less
new file mode 100644
index 00000000..11ac5fa5
--- /dev/null
+++ b/test/options/sort-order/process/less/mixin-1.expected.less
@@ -0,0 +1,12 @@
+.bordered {
+ border-top: dotted 1px black;
+ border-bottom: solid 2px black;
+}
+#menu a {
+ .bordered;
+ color: #111;
+ }
+.post a {
+ .bordered;
+ color: red;
+ }
diff --git a/test/options/sort-order/process/less/mixin-1.less b/test/options/sort-order/process/less/mixin-1.less
new file mode 100644
index 00000000..e5d5ad0b
--- /dev/null
+++ b/test/options/sort-order/process/less/mixin-1.less
@@ -0,0 +1,12 @@
+.bordered {
+ border-bottom: solid 2px black;
+ border-top: dotted 1px black;
+}
+#menu a {
+ color: #111;
+ .bordered;
+ }
+.post a {
+ color: red;
+ .bordered;
+ }
diff --git a/test/options/sort-order/process/less/mixin-2.expected.less b/test/options/sort-order/process/less/mixin-2.expected.less
new file mode 100644
index 00000000..1535db16
--- /dev/null
+++ b/test/options/sort-order/process/less/mixin-2.expected.less
@@ -0,0 +1,6 @@
+.test {
+ .test1();
+ .test2();
+ top: 0;
+ color: tomato;
+ }
diff --git a/test/options/sort-order/process/less/mixin-2.less b/test/options/sort-order/process/less/mixin-2.less
new file mode 100644
index 00000000..cbd0c8ea
--- /dev/null
+++ b/test/options/sort-order/process/less/mixin-2.less
@@ -0,0 +1,6 @@
+.test {
+ .test1();
+ color: tomato;
+ .test2();
+ top: 0;
+ }
diff --git a/test/options/sort-order/process/less/mixin-3.expected.less b/test/options/sort-order/process/less/mixin-3.expected.less
new file mode 100644
index 00000000..0e4c018d
--- /dev/null
+++ b/test/options/sort-order/process/less/mixin-3.expected.less
@@ -0,0 +1,5 @@
+.foo {
+ .linear-gradient(#fff; #000);
+ border: 1px solid #f00;
+ color: #0f0;
+}
diff --git a/test/options/sort-order/process/less/mixin-3.less b/test/options/sort-order/process/less/mixin-3.less
new file mode 100644
index 00000000..fce214a5
--- /dev/null
+++ b/test/options/sort-order/process/less/mixin-3.less
@@ -0,0 +1,5 @@
+.foo {
+ color: #0f0;
+ border: 1px solid #f00;
+ .linear-gradient(#fff; #000);
+}
diff --git a/test/options/sort-order/process/less/mixin-4.expected.less b/test/options/sort-order/process/less/mixin-4.expected.less
new file mode 100644
index 00000000..94c24ed9
--- /dev/null
+++ b/test/options/sort-order/process/less/mixin-4.expected.less
@@ -0,0 +1,9 @@
+.block {
+ .last;
+ .hide-text;
+
+ color: black;
+
+ .media("lap");
+ .media("palm");
+}
diff --git a/test/options/sort-order/process/less/mixin-4.less b/test/options/sort-order/process/less/mixin-4.less
new file mode 100644
index 00000000..b405880c
--- /dev/null
+++ b/test/options/sort-order/process/less/mixin-4.less
@@ -0,0 +1,7 @@
+.block {
+ color: black;
+ .media("lap");
+ .media("palm");
+ .last;
+ .hide-text;
+}
diff --git a/test/options/sort-order/process/less/nested-rule-1.expected.less b/test/options/sort-order/process/less/nested-rule-1.expected.less
new file mode 100644
index 00000000..e85581f5
--- /dev/null
+++ b/test/options/sort-order/process/less/nested-rule-1.expected.less
@@ -0,0 +1,6 @@
+div {
+ color: tomato; a {
+ top: 0;
+ color: nani;
+ }
+}
diff --git a/test/options/sort-order/process/less/nested-rule-1.less b/test/options/sort-order/process/less/nested-rule-1.less
new file mode 100644
index 00000000..af115947
--- /dev/null
+++ b/test/options/sort-order/process/less/nested-rule-1.less
@@ -0,0 +1,6 @@
+div {
+ color: tomato; a {
+ color: nani;
+ top: 0;
+ }
+}
diff --git a/test/options/sort-order/process/less/nested-rule-2.expected.less b/test/options/sort-order/process/less/nested-rule-2.expected.less
new file mode 100644
index 00000000..30c8b653
--- /dev/null
+++ b/test/options/sort-order/process/less/nested-rule-2.expected.less
@@ -0,0 +1,7 @@
+div {
+ left: 0;
+ color: tomato; a {
+ top: 0;
+ color: nani;
+ }
+}
diff --git a/test/options/sort-order/process/less/nested-rule-2.less b/test/options/sort-order/process/less/nested-rule-2.less
new file mode 100644
index 00000000..52df5998
--- /dev/null
+++ b/test/options/sort-order/process/less/nested-rule-2.less
@@ -0,0 +1,7 @@
+div {
+ color: tomato; a {
+ color: nani;
+ top: 0;
+ }
+ left: 0;
+}
diff --git a/test/options/sort-order/process/less/rule.expected.less b/test/options/sort-order/process/less/rule.expected.less
new file mode 100644
index 00000000..918bd37c
--- /dev/null
+++ b/test/options/sort-order/process/less/rule.expected.less
@@ -0,0 +1,4 @@
+div {
+ top: 0;
+ color: tomato;
+}
diff --git a/test/options/sort-order/process/less/rule.less b/test/options/sort-order/process/less/rule.less
new file mode 100644
index 00000000..7ffa908a
--- /dev/null
+++ b/test/options/sort-order/process/less/rule.less
@@ -0,0 +1,4 @@
+div {
+ color: tomato;
+ top: 0;
+}
diff --git a/test/options/sort-order/process/less/variable.expected.less b/test/options/sort-order/process/less/variable.expected.less
new file mode 100644
index 00000000..e391754f
--- /dev/null
+++ b/test/options/sort-order/process/less/variable.expected.less
@@ -0,0 +1 @@
+div {@red: tomato; color: @red; }
diff --git a/test/options/sort-order/process/less/variable.less b/test/options/sort-order/process/less/variable.less
new file mode 100644
index 00000000..5c6756b9
--- /dev/null
+++ b/test/options/sort-order/process/less/variable.less
@@ -0,0 +1 @@
+div { color: @red; @red: tomato; }
diff --git a/test/options/sort-order/process/sass/comments.expected.sass b/test/options/sort-order/process/sass/comments.expected.sass
new file mode 100644
index 00000000..a7986421
--- /dev/null
+++ b/test/options/sort-order/process/sass/comments.expected.sass
@@ -0,0 +1,6 @@
+div
+ /* 2 */
+ /* 3 */
+ top: 0 // 4
+ color: tomato // 1
+ /* 5 */
diff --git a/test/options/sort-order/process/sass/comments.sass b/test/options/sort-order/process/sass/comments.sass
new file mode 100644
index 00000000..5f3f7cc7
--- /dev/null
+++ b/test/options/sort-order/process/sass/comments.sass
@@ -0,0 +1,6 @@
+div
+ color: tomato // 1
+ /* 2 */
+ /* 3 */
+ top: 0 // 4
+ /* 5 */
diff --git a/test/options/sort-order/process/sass/condition.expected.sass b/test/options/sort-order/process/sass/condition.expected.sass
new file mode 100644
index 00000000..8ffeb9f6
--- /dev/null
+++ b/test/options/sort-order/process/sass/condition.expected.sass
@@ -0,0 +1,6 @@
+div
+ top: 0
+ color: tomato
+ @if $color == tomato
+ font-size: 2px
+ display: block
diff --git a/test/options/sort-order/process/sass/condition.sass b/test/options/sort-order/process/sass/condition.sass
new file mode 100644
index 00000000..66379eea
--- /dev/null
+++ b/test/options/sort-order/process/sass/condition.sass
@@ -0,0 +1,6 @@
+div
+ color: tomato
+ @if $color == tomato
+ display: block
+ font-size: 2px
+ top: 0
diff --git a/test/options/sort-order/process/sass/different-groups.expected.sass b/test/options/sort-order/process/sass/different-groups.expected.sass
new file mode 100644
index 00000000..2ffd1993
--- /dev/null
+++ b/test/options/sort-order/process/sass/different-groups.expected.sass
@@ -0,0 +1,4 @@
+div
+ top: 0
+
+ color: tomato
diff --git a/test/options/sort-order/process/sass/different-groups.sass b/test/options/sort-order/process/sass/different-groups.sass
new file mode 100644
index 00000000..6d835e28
--- /dev/null
+++ b/test/options/sort-order/process/sass/different-groups.sass
@@ -0,0 +1,3 @@
+div
+ color: tomato
+ top: 0
diff --git a/test/options/sort-order/process/sass/extend.expected.sass b/test/options/sort-order/process/sass/extend.expected.sass
new file mode 100644
index 00000000..62ecc2e7
--- /dev/null
+++ b/test/options/sort-order/process/sass/extend.expected.sass
@@ -0,0 +1,3 @@
+div
+ @extend %nani
+ color: tomato
diff --git a/test/options/sort-order/process/sass/extend.sass b/test/options/sort-order/process/sass/extend.sass
new file mode 100644
index 00000000..e4e3550d
--- /dev/null
+++ b/test/options/sort-order/process/sass/extend.sass
@@ -0,0 +1,3 @@
+div
+ color: tomato
+ @extend %nani
diff --git a/test/options/sort-order/process/sass/import.expected.sass b/test/options/sort-order/process/sass/import.expected.sass
new file mode 100644
index 00000000..f1f2c6a5
--- /dev/null
+++ b/test/options/sort-order/process/sass/import.expected.sass
@@ -0,0 +1,3 @@
+ div
+ @import "foo.css"
+ color: tomato
diff --git a/test/options/sort-order/process/sass/import.sass b/test/options/sort-order/process/sass/import.sass
new file mode 100644
index 00000000..659f76ee
--- /dev/null
+++ b/test/options/sort-order/process/sass/import.sass
@@ -0,0 +1,3 @@
+ div
+ color: tomato
+ @import "foo.css"
diff --git a/test/options/sort-order/process/sass/include-specified-1.expected.sass b/test/options/sort-order/process/sass/include-specified-1.expected.sass
new file mode 100644
index 00000000..dbc734c2
--- /dev/null
+++ b/test/options/sort-order/process/sass/include-specified-1.expected.sass
@@ -0,0 +1,10 @@
+.block
+ +last
+ +hide-text
+
+ color: black
+
+ +media(lap)
+ color: green
+ +media(palm)
+ color: red
diff --git a/test/options/sort-order/process/sass/include-specified-1.sass b/test/options/sort-order/process/sass/include-specified-1.sass
new file mode 100644
index 00000000..5782d546
--- /dev/null
+++ b/test/options/sort-order/process/sass/include-specified-1.sass
@@ -0,0 +1,10 @@
+.block
+ color: black
+ +media(lap)
+ color: green
+
+ +media(palm)
+ color: red
+
+ +last
+ +hide-text
diff --git a/test/options/sort-order/process/sass/include-specified-2.expected.sass b/test/options/sort-order/process/sass/include-specified-2.expected.sass
new file mode 100644
index 00000000..75cfdaae
--- /dev/null
+++ b/test/options/sort-order/process/sass/include-specified-2.expected.sass
@@ -0,0 +1,10 @@
+.block
+ @include last
+ @include hide-text
+
+ color: black
+
+ @include media(lap)
+ color: green
+ @include media(palm)
+ color: red
diff --git a/test/options/sort-order/process/sass/include-specified-2.sass b/test/options/sort-order/process/sass/include-specified-2.sass
new file mode 100644
index 00000000..9ca4de9c
--- /dev/null
+++ b/test/options/sort-order/process/sass/include-specified-2.sass
@@ -0,0 +1,10 @@
+.block
+ color: black
+ @include media(lap)
+ color: green
+
+ @include media(palm)
+ color: red
+
+ @include last
+ @include hide-text
diff --git a/test/options/sort-order/process/sass/include.expected.sass b/test/options/sort-order/process/sass/include.expected.sass
new file mode 100644
index 00000000..86ab0463
--- /dev/null
+++ b/test/options/sort-order/process/sass/include.expected.sass
@@ -0,0 +1,3 @@
+div
+ @include nani
+ color: tomato
diff --git a/test/options/sort-order/process/sass/include.sass b/test/options/sort-order/process/sass/include.sass
new file mode 100644
index 00000000..5b42a65f
--- /dev/null
+++ b/test/options/sort-order/process/sass/include.sass
@@ -0,0 +1,3 @@
+div
+ color: tomato
+ @include nani
diff --git a/test/options/sort-order/process/sass/issue-332-2.expected.sass b/test/options/sort-order/process/sass/issue-332-2.expected.sass
new file mode 100644
index 00000000..2c86fb96
--- /dev/null
+++ b/test/options/sort-order/process/sass/issue-332-2.expected.sass
@@ -0,0 +1,46 @@
+$contest_color: #ffc33c
+
+.winners_images_button
+ display: none
+.winners_images
+ height: 100px
+ padding-left: 0.6%
+ padding-right: 0.6%
+ width: 100%
+ .winner_cell
+ float: left
+ height: 100px
+ width: 85px
+ .winner_avatar
+ background-repeat: no-repeat
+ background-size: 100%
+ border: 1px solid lightgray
+ float: left
+ height: 75px
+ width: 85px
+ .winner_year
+ color: black
+ float: left
+ font-family: bold_mic_font
+ font-size: 17px
+ height: 20px
+ margin-bottom: 5px
+ text-align: center
+ width: 85px
+ span
+ display: none
+ .winner_info
+ display: none
+ .winner_cell:hover
+ .winner_year
+ span
+ display: block
+ .tooltip-inner
+ font-size: 10pt
+ text-align: justify
+ h6
+ color: white
+ font-family: bold_mic_font
+ font-size: 12pt
+ margin: 0
+ text-align: center
diff --git a/test/options/sort-order/process/sass/issue-332-2.sass b/test/options/sort-order/process/sass/issue-332-2.sass
new file mode 100644
index 00000000..d926fa86
--- /dev/null
+++ b/test/options/sort-order/process/sass/issue-332-2.sass
@@ -0,0 +1,46 @@
+$contest_color: #ffc33c
+
+.winners_images_button
+ display: none
+.winners_images
+ width: 100%
+ padding-left: 0.6%
+ padding-right: 0.6%
+ height: 100px
+ .winner_cell
+ width: 85px
+ height: 100px
+ float: left
+ .winner_avatar
+ width: 85px
+ height: 75px
+ border: 1px solid lightgray
+ float: left
+ background-repeat: no-repeat
+ background-size: 100%
+ .winner_year
+ margin-bottom: 5px
+ text-align: center
+ float: left
+ width: 85px
+ height: 20px
+ font-size: 17px
+ color: black
+ font-family: bold_mic_font
+ span
+ display: none
+ .winner_info
+ display: none
+ .winner_cell:hover
+ .winner_year
+ span
+ display: block
+ .tooltip-inner
+ h6
+ text-align: center
+ margin: 0
+ font-family: bold_mic_font
+ color: white
+ font-size: 12pt
+ font-size: 10pt
+ text-align: justify
diff --git a/test/options/sort-order/process/sass/issue-332.expected.sass b/test/options/sort-order/process/sass/issue-332.expected.sass
new file mode 100644
index 00000000..390beee6
--- /dev/null
+++ b/test/options/sort-order/process/sass/issue-332.expected.sass
@@ -0,0 +1,9 @@
+.block
+ color: black
+
+ +media(lap)
+ color: green
+ +media(palm)
+ color: red
+ +last
+ +hide-text
diff --git a/test/options/sort-order/process/sass/issue-332.sass b/test/options/sort-order/process/sass/issue-332.sass
new file mode 100644
index 00000000..3eee3b09
--- /dev/null
+++ b/test/options/sort-order/process/sass/issue-332.sass
@@ -0,0 +1,11 @@
+.block
+ +media(lap)
+ color: green
+
+ +media(palm)
+ color: red
+
+ color: black
+
+ +last
+ +hide-text
diff --git a/test/options/sort-order/process/sass/mixin.expected.sass b/test/options/sort-order/process/sass/mixin.expected.sass
new file mode 100644
index 00000000..e4410624
--- /dev/null
+++ b/test/options/sort-order/process/sass/mixin.expected.sass
@@ -0,0 +1,4 @@
+.foo
+ @include nani
+ top: 0
+ color: tomato
diff --git a/test/options/sort-order/process/sass/mixin.sass b/test/options/sort-order/process/sass/mixin.sass
new file mode 100644
index 00000000..d22987c8
--- /dev/null
+++ b/test/options/sort-order/process/sass/mixin.sass
@@ -0,0 +1,4 @@
+.foo
+ @include nani
+ color: tomato
+ top: 0
diff --git a/test/options/sort-order/process/sass/nested-rule-1.expected.sass b/test/options/sort-order/process/sass/nested-rule-1.expected.sass
new file mode 100644
index 00000000..101521f8
--- /dev/null
+++ b/test/options/sort-order/process/sass/nested-rule-1.expected.sass
@@ -0,0 +1,5 @@
+div
+ color: tomato
+ a
+ top: 0
+ color: nani
diff --git a/test/options/sort-order/process/sass/nested-rule-1.sass b/test/options/sort-order/process/sass/nested-rule-1.sass
new file mode 100644
index 00000000..3394a7c1
--- /dev/null
+++ b/test/options/sort-order/process/sass/nested-rule-1.sass
@@ -0,0 +1,5 @@
+div
+ color: tomato
+ a
+ color: nani
+ top: 0
diff --git a/test/options/sort-order/process/sass/nested-rule-2.expected.sass b/test/options/sort-order/process/sass/nested-rule-2.expected.sass
new file mode 100644
index 00000000..54a0fb80
--- /dev/null
+++ b/test/options/sort-order/process/sass/nested-rule-2.expected.sass
@@ -0,0 +1,6 @@
+div
+ left: 0
+ color: tomato
+ a
+ top: 0
+ color: nani
diff --git a/test/options/sort-order/process/sass/nested-rule-2.sass b/test/options/sort-order/process/sass/nested-rule-2.sass
new file mode 100644
index 00000000..9815840e
--- /dev/null
+++ b/test/options/sort-order/process/sass/nested-rule-2.sass
@@ -0,0 +1,6 @@
+div
+ color: tomato
+ a
+ color: nani
+ top: 0
+ left: 0
diff --git a/test/options/sort-order/process/sass/rule.expected.sass b/test/options/sort-order/process/sass/rule.expected.sass
new file mode 100644
index 00000000..c6e1f549
--- /dev/null
+++ b/test/options/sort-order/process/sass/rule.expected.sass
@@ -0,0 +1,3 @@
+div
+ top: 0
+ color: tomato
diff --git a/test/options/sort-order/process/sass/rule.sass b/test/options/sort-order/process/sass/rule.sass
new file mode 100644
index 00000000..6d835e28
--- /dev/null
+++ b/test/options/sort-order/process/sass/rule.sass
@@ -0,0 +1,3 @@
+div
+ color: tomato
+ top: 0
diff --git a/test/options/sort-order/process/sass/ruleset.expected.sass b/test/options/sort-order/process/sass/ruleset.expected.sass
new file mode 100644
index 00000000..54a0fb80
--- /dev/null
+++ b/test/options/sort-order/process/sass/ruleset.expected.sass
@@ -0,0 +1,6 @@
+div
+ left: 0
+ color: tomato
+ a
+ top: 0
+ color: nani
diff --git a/test/options/sort-order/process/sass/ruleset.sass b/test/options/sort-order/process/sass/ruleset.sass
new file mode 100644
index 00000000..9815840e
--- /dev/null
+++ b/test/options/sort-order/process/sass/ruleset.sass
@@ -0,0 +1,6 @@
+div
+ color: tomato
+ a
+ color: nani
+ top: 0
+ left: 0
diff --git a/test/options/sort-order/process/sass/variable.expected.sass b/test/options/sort-order/process/sass/variable.expected.sass
new file mode 100644
index 00000000..c7c3634f
--- /dev/null
+++ b/test/options/sort-order/process/sass/variable.expected.sass
@@ -0,0 +1,3 @@
+div
+ $red: tomato
+ color: $tomato
diff --git a/test/options/sort-order/process/sass/variable.sass b/test/options/sort-order/process/sass/variable.sass
new file mode 100644
index 00000000..31bc5a3c
--- /dev/null
+++ b/test/options/sort-order/process/sass/variable.sass
@@ -0,0 +1,3 @@
+div
+ color: $tomato
+ $red: tomato
diff --git a/test/options/sort-order/process/scss/comments-1.expected.scss b/test/options/sort-order/process/scss/comments-1.expected.scss
new file mode 100644
index 00000000..0aa257a7
--- /dev/null
+++ b/test/options/sort-order/process/scss/comments-1.expected.scss
@@ -0,0 +1,7 @@
+div {
+ /* 2 */
+ /* 3 */
+ top: 0; /* 4 */
+ color: tomato; /* 1 */
+ /* 5 */
+}
diff --git a/test/options/sort-order/process/scss/comments-1.scss b/test/options/sort-order/process/scss/comments-1.scss
new file mode 100644
index 00000000..18e6319b
--- /dev/null
+++ b/test/options/sort-order/process/scss/comments-1.scss
@@ -0,0 +1,7 @@
+div {
+ color: tomato; /* 1 */
+ /* 2 */
+ /* 3 */
+ top: 0; /* 4 */
+ /* 5 */
+}
diff --git a/test/options/sort-order/process/scss/comments-2.expected.scss b/test/options/sort-order/process/scss/comments-2.expected.scss
new file mode 100644
index 00000000..a9b2976c
--- /dev/null
+++ b/test/options/sort-order/process/scss/comments-2.expected.scss
@@ -0,0 +1 @@
+div {top: 0; /* 3 */ /* 4 *//* 1 */ color: tomato; /* 2 */ }
diff --git a/test/options/sort-order/process/scss/comments-2.scss b/test/options/sort-order/process/scss/comments-2.scss
new file mode 100644
index 00000000..617d4fdc
--- /dev/null
+++ b/test/options/sort-order/process/scss/comments-2.scss
@@ -0,0 +1 @@
+div {/* 1 */ color: tomato; /* 2 */ top: 0; /* 3 */ /* 4 */}
diff --git a/test/options/sort-order/process/scss/condition.expected.scss b/test/options/sort-order/process/scss/condition.expected.scss
new file mode 100644
index 00000000..e56af512
--- /dev/null
+++ b/test/options/sort-order/process/scss/condition.expected.scss
@@ -0,0 +1,8 @@
+div {
+ top: 0;
+ color: tomato;
+ @if @color == tomato {
+ font-size: 2px;
+ display: block;
+ }
+ }
diff --git a/test/options/sort-order/process/scss/condition.scss b/test/options/sort-order/process/scss/condition.scss
new file mode 100644
index 00000000..9211813d
--- /dev/null
+++ b/test/options/sort-order/process/scss/condition.scss
@@ -0,0 +1,8 @@
+div {
+ color: tomato;
+ @if @color == tomato {
+ display: block;
+ font-size: 2px;
+ }
+ top: 0;
+ }
diff --git a/test/options/sort-order/process/scss/different-groups.expected.scss b/test/options/sort-order/process/scss/different-groups.expected.scss
new file mode 100644
index 00000000..dddc16f8
--- /dev/null
+++ b/test/options/sort-order/process/scss/different-groups.expected.scss
@@ -0,0 +1,5 @@
+div {
+ top: 0;
+
+ color: tomato;
+}
diff --git a/test/options/sort-order/process/scss/different-groups.scss b/test/options/sort-order/process/scss/different-groups.scss
new file mode 100644
index 00000000..7ffa908a
--- /dev/null
+++ b/test/options/sort-order/process/scss/different-groups.scss
@@ -0,0 +1,4 @@
+div {
+ color: tomato;
+ top: 0;
+}
diff --git a/test/options/sort-order/process/scss/extend.expected.scss b/test/options/sort-order/process/scss/extend.expected.scss
new file mode 100644
index 00000000..a1ac4c71
--- /dev/null
+++ b/test/options/sort-order/process/scss/extend.expected.scss
@@ -0,0 +1 @@
+div {@extend %nani; color: tomato; }
diff --git a/test/options/sort-order/process/scss/extend.scss b/test/options/sort-order/process/scss/extend.scss
new file mode 100644
index 00000000..381bc927
--- /dev/null
+++ b/test/options/sort-order/process/scss/extend.scss
@@ -0,0 +1 @@
+div { color: tomato; @extend %nani; }
diff --git a/test/options/sort-order/process/scss/import.expected.scss b/test/options/sort-order/process/scss/import.expected.scss
new file mode 100644
index 00000000..9c403dfe
--- /dev/null
+++ b/test/options/sort-order/process/scss/import.expected.scss
@@ -0,0 +1 @@
+div {@import "foo.css"; color: tomato; }
diff --git a/test/options/sort-order/process/scss/import.scss b/test/options/sort-order/process/scss/import.scss
new file mode 100644
index 00000000..681ccd06
--- /dev/null
+++ b/test/options/sort-order/process/scss/import.scss
@@ -0,0 +1 @@
+div { color: tomato; @import "foo.css"; }
diff --git a/test/options/sort-order/process/scss/include-specified.expected.scss b/test/options/sort-order/process/scss/include-specified.expected.scss
new file mode 100644
index 00000000..412f47d2
--- /dev/null
+++ b/test/options/sort-order/process/scss/include-specified.expected.scss
@@ -0,0 +1,13 @@
+.block {
+ @include last;
+ @include hide-text;
+
+ color: black;
+
+ @include media("lap") {
+ color: green;
+ }
+ @include media("palm") {
+ color: red;
+ }
+}
diff --git a/test/options/sort-order/process/scss/include-specified.scss b/test/options/sort-order/process/scss/include-specified.scss
new file mode 100644
index 00000000..40b8c98e
--- /dev/null
+++ b/test/options/sort-order/process/scss/include-specified.scss
@@ -0,0 +1,11 @@
+.block {
+ color: black;
+ @include media("lap") {
+ color: green;
+ }
+ @include media("palm") {
+ color: red;
+ }
+ @include last;
+ @include hide-text;
+}
diff --git a/test/options/sort-order/process/scss/include.expected.scss b/test/options/sort-order/process/scss/include.expected.scss
new file mode 100644
index 00000000..390cd58f
--- /dev/null
+++ b/test/options/sort-order/process/scss/include.expected.scss
@@ -0,0 +1 @@
+div {@include nani; color: tomato; }
diff --git a/test/options/sort-order/process/scss/include.scss b/test/options/sort-order/process/scss/include.scss
new file mode 100644
index 00000000..2feaaec8
--- /dev/null
+++ b/test/options/sort-order/process/scss/include.scss
@@ -0,0 +1 @@
+div { color: tomato; @include nani; }
diff --git a/test/options/sort-order/process/scss/issue-317.scss b/test/options/sort-order/process/scss/issue-317.scss
new file mode 100644
index 00000000..f3589f42
--- /dev/null
+++ b/test/options/sort-order/process/scss/issue-317.scss
@@ -0,0 +1,31 @@
+.tv-header-reminders {
+ display: inline-block;
+ position: relative;
+ vertical-align: top;
+
+ &__spin {
+ background-color: #fff;
+ display: none;
+ height: 100%;
+ left: 0;
+ position: absolute;
+ top: 0;
+ width: 100%;
+
+ .spin {
+ height: 100%;
+ text-align: center;
+ }
+
+ .spin__icon {
+ margin-top: 4px;
+ }
+ }
+
+ &_loading_yes .tv-header-reminders__spin {
+ display: block;
+
+
+ }
+
+}
diff --git a/test/options/sort-order/process/scss/issue-333.scss b/test/options/sort-order/process/scss/issue-333.scss
new file mode 100644
index 00000000..28a52fde
--- /dev/null
+++ b/test/options/sort-order/process/scss/issue-333.scss
@@ -0,0 +1,5 @@
+@mixin opacity($opacity) {
+ $opacity-ie: $opacity * 100;
+ opacity: $opacity;
+ filter: alpha(opacity=50);
+}
diff --git a/test/options/sort-order/process/scss/issue-399.expected.scss b/test/options/sort-order/process/scss/issue-399.expected.scss
new file mode 100644
index 00000000..bf69f21b
--- /dev/null
+++ b/test/options/sort-order/process/scss/issue-399.expected.scss
@@ -0,0 +1,14 @@
+.block {
+ @extend .hey;
+ @extend %ho;
+ color: black;
+
+ @include media("lap") {
+ color: green;
+ }
+ @include media("palm") {
+ color: red;
+ }
+ @include last;
+ @include hide-text;
+}
diff --git a/test/options/sort-order/process/scss/issue-399.scss b/test/options/sort-order/process/scss/issue-399.scss
new file mode 100644
index 00000000..7e090c81
--- /dev/null
+++ b/test/options/sort-order/process/scss/issue-399.scss
@@ -0,0 +1,15 @@
+.block {
+ color: black;
+
+ @extend .hey;
+ @extend %ho;
+
+ @include media("lap") {
+ color: green;
+ }
+ @include media("palm") {
+ color: red;
+ }
+ @include last;
+ @include hide-text;
+}
diff --git a/test/options/sort-order/process/scss/leftovers.expected.scss b/test/options/sort-order/process/scss/leftovers.expected.scss
new file mode 100644
index 00000000..d431a118
--- /dev/null
+++ b/test/options/sort-order/process/scss/leftovers.expected.scss
@@ -0,0 +1,36 @@
+a {
+ $red: tomato;
+
+ position: $tomato;
+
+ leftover: yay;
+ border: 1px solid;
+
+ @include nani;
+
+ font: 20px/16px Arial, sans-serif;
+}
+b {
+ $red: tomato;
+
+ position: $tomato;
+
+ leftover: yay;
+ border: 1px solid;
+
+ @include nani;
+
+ font: 20px/16px Arial, sans-serif;
+}
+c {
+ $red: tomato;
+
+ position: $tomato;
+
+ leftover: yay;
+ border: 1px solid;
+
+ @include nani;
+
+ font: 20px/16px Arial, sans-serif;
+}
diff --git a/test/options/sort-order/process/scss/leftovers.scss b/test/options/sort-order/process/scss/leftovers.scss
new file mode 100644
index 00000000..ff5bc4df
--- /dev/null
+++ b/test/options/sort-order/process/scss/leftovers.scss
@@ -0,0 +1,24 @@
+a {
+ leftover: yay;
+ border: 1px solid;
+ @include nani;
+ font: 20px/16px Arial, sans-serif;
+ position: $tomato;
+ $red: tomato;
+}
+b {
+ border: 1px solid;
+ @include nani;
+ position: $tomato;
+ leftover: yay;
+ font: 20px/16px Arial, sans-serif;
+ $red: tomato;
+}
+c {
+ border: 1px solid;
+ @include nani;
+ font: 20px/16px Arial, sans-serif;
+ position: $tomato;
+ $red: tomato;
+ leftover: yay;
+}
diff --git a/test/options/sort-order/process/scss/mixin.expected.scss b/test/options/sort-order/process/scss/mixin.expected.scss
new file mode 100644
index 00000000..abfbef4e
--- /dev/null
+++ b/test/options/sort-order/process/scss/mixin.expected.scss
@@ -0,0 +1 @@
+.foo { @include nani {top: 0; color: tomato; } }
diff --git a/test/options/sort-order/process/scss/mixin.scss b/test/options/sort-order/process/scss/mixin.scss
new file mode 100644
index 00000000..b174d3bd
--- /dev/null
+++ b/test/options/sort-order/process/scss/mixin.scss
@@ -0,0 +1 @@
+.foo { @include nani { color: tomato; top: 0; } }
diff --git a/test/options/sort-order/process/scss/nested-rule-1.expected.scss b/test/options/sort-order/process/scss/nested-rule-1.expected.scss
new file mode 100644
index 00000000..54dada66
--- /dev/null
+++ b/test/options/sort-order/process/scss/nested-rule-1.expected.scss
@@ -0,0 +1 @@
+div { color: tomato; a {top: 0; color: nani; } }
diff --git a/test/options/sort-order/process/scss/nested-rule-1.scss b/test/options/sort-order/process/scss/nested-rule-1.scss
new file mode 100644
index 00000000..ee50453a
--- /dev/null
+++ b/test/options/sort-order/process/scss/nested-rule-1.scss
@@ -0,0 +1 @@
+div { color: tomato; a { color: nani; top: 0; } }
diff --git a/test/options/sort-order/process/scss/nested-rule-2.expected.scss b/test/options/sort-order/process/scss/nested-rule-2.expected.scss
new file mode 100644
index 00000000..f5c2e9fd
--- /dev/null
+++ b/test/options/sort-order/process/scss/nested-rule-2.expected.scss
@@ -0,0 +1 @@
+div { left: 0; color: tomato; a {top: 0; color: nani; }}
diff --git a/test/options/sort-order/process/scss/nested-rule-2.scss b/test/options/sort-order/process/scss/nested-rule-2.scss
new file mode 100644
index 00000000..0bc62125
--- /dev/null
+++ b/test/options/sort-order/process/scss/nested-rule-2.scss
@@ -0,0 +1 @@
+div { color: tomato; a { color: nani; top: 0; } left: 0; }
diff --git a/test/options/sort-order/process/scss/rule-multiline.expected.scss b/test/options/sort-order/process/scss/rule-multiline.expected.scss
new file mode 100644
index 00000000..5f6d5e30
--- /dev/null
+++ b/test/options/sort-order/process/scss/rule-multiline.expected.scss
@@ -0,0 +1,4 @@
+div {
+ top: 0;
+ color: tomato;
+ }
diff --git a/test/options/sort-order/process/scss/rule-multiline.scss b/test/options/sort-order/process/scss/rule-multiline.scss
new file mode 100644
index 00000000..8ae29314
--- /dev/null
+++ b/test/options/sort-order/process/scss/rule-multiline.scss
@@ -0,0 +1,4 @@
+div {
+ color: tomato;
+ top: 0;
+ }
diff --git a/test/options/sort-order/process/scss/rule.expected.scss b/test/options/sort-order/process/scss/rule.expected.scss
new file mode 100644
index 00000000..a7669bdf
--- /dev/null
+++ b/test/options/sort-order/process/scss/rule.expected.scss
@@ -0,0 +1 @@
+div {top: 0; color: tomato; }
diff --git a/test/options/sort-order/process/scss/rule.scss b/test/options/sort-order/process/scss/rule.scss
new file mode 100644
index 00000000..561b980a
--- /dev/null
+++ b/test/options/sort-order/process/scss/rule.scss
@@ -0,0 +1 @@
+div { color: tomato; top: 0; }
diff --git a/test/options/sort-order/process/scss/ruleset.expected.scss b/test/options/sort-order/process/scss/ruleset.expected.scss
new file mode 100644
index 00000000..194ff87b
--- /dev/null
+++ b/test/options/sort-order/process/scss/ruleset.expected.scss
@@ -0,0 +1,8 @@
+div {
+ left: 0;
+ color: tomato;
+ a {
+ top: 0;
+ color: nani;
+ }
+}
diff --git a/test/options/sort-order/process/scss/ruleset.scss b/test/options/sort-order/process/scss/ruleset.scss
new file mode 100644
index 00000000..d7ed6412
--- /dev/null
+++ b/test/options/sort-order/process/scss/ruleset.scss
@@ -0,0 +1,8 @@
+div {
+ color: tomato;
+ a {
+ color: nani;
+ top: 0;
+ }
+ left: 0;
+}
diff --git a/test/options/sort-order/process/scss/variable.expected.scss b/test/options/sort-order/process/scss/variable.expected.scss
new file mode 100644
index 00000000..c17c6341
--- /dev/null
+++ b/test/options/sort-order/process/scss/variable.expected.scss
@@ -0,0 +1 @@
+div {$red: tomato; color: $tomato; }
diff --git a/test/options/sort-order/process/scss/variable.scss b/test/options/sort-order/process/scss/variable.scss
new file mode 100644
index 00000000..f759f22c
--- /dev/null
+++ b/test/options/sort-order/process/scss/variable.scss
@@ -0,0 +1 @@
+div { color: $tomato; $red: tomato; }
diff --git a/test/options/sort-order/process/test.js b/test/options/sort-order/process/test.js
new file mode 100644
index 00000000..6df33154
--- /dev/null
+++ b/test/options/sort-order/process/test.js
@@ -0,0 +1,493 @@
+var assert = require('assert');
+let Test = require('../../option_test');
+
+describe('Option `sort-order`, process', function() {
+ describe('css', function() {
+ it('Should be in expected order in case properties are not grouped', function() {
+ let test = new Test(this, {'sort-order': ['position', 'z-index']});
+ return test.shouldBeEqual('single-group.css', 'single-group.expected.css');
+ });
+
+ it('Should be in expected order in case of 1 group', function() {
+ let test = new Test(this, {'sort-order': [['position', 'z-index']]});
+ return test.shouldBeEqual('single-group.css', 'single-group.expected.css');
+ });
+
+ it('Shuld be in expected order in case of multiple groups', function() {
+ let test = new Test(this, {
+ 'sort-order': [
+ ['position', 'z-index'],
+ ['width', 'height']
+ ]
+ });
+ return test.shouldBeEqual('multiple-groups.css', 'multiple-groups.expected.css');
+ });
+
+ it('Should work correctly with comments in case of 1 group', function() {
+ let test = new Test(this, {
+ 'sort-order': [
+ ['border-bottom', 'font-style'],
+ ]
+ });
+ return test.shouldBeEqual('single-group-comments.css', 'single-group-comments.expected.css');
+ });
+
+ it('Should work correctly with comments in case of multiple groups', function() {
+ let test = new Test(this, {
+ 'sort-order': [
+ ['margin'], ['padding']
+ ]
+ });
+ return test.shouldBeEqual('multiple-groups-comments.css', 'multiple-groups-comments.expected.css');
+ });
+
+ it('Should parse semicolons inside data uri correctly', function() {
+ let test = new Test(this, {
+ 'sort-order': [
+ ['position', 'background', 'color']
+ ]
+ });
+ return test.shouldBeEqual('data-uri.css', 'data-uri.expected.css');
+ });
+
+ it('Should not add more than 1 line between groups', function() {
+ let test = new Test(this, {
+ 'sort-order': [
+ ['top'], ['color']
+ ]
+ });
+ let input = test.readFile('multiple-groups-2.css');
+ let expected = test.readFile('multiple-groups-2.expected.css');
+
+ test.comb.processString(input)
+ .then(test.comb.processString)
+ .then(test.comb.processString)
+ .then(test.comb.processString)
+ .then(test.comb.processString)
+ .then(test.comb.processString)
+ .then(function(actual) {
+ assert.equal(actual, expected);
+ });
+ });
+
+ it('Issue 94. Test 1', function() {
+ let test = new Test(this);
+ test.useConfig('csscomb');
+ return test.shouldBeEqual('issue-94-1.css', 'issue-94-1.expected.css');
+ });
+
+ it('Issue 94. Test 2', function() {
+ let test = new Test(this);
+ test.useConfig('csscomb');
+ return test.shouldBeEqual('issue-94-2.css', 'issue-94-2.expected.css');
+ });
+
+ it('Issue 94. Test 3', function() {
+ let test = new Test(this);
+ test.useConfig('csscomb');
+ return test.shouldBeEqual('issue-94-3.css', 'issue-94-3.expected.css');
+ });
+
+ it('Should place the leftovers in the end', function() {
+ let test = new Test(this);
+ test.useConfig('csscomb');
+ return test.shouldBeEqual('leftovers-1.css', 'leftovers-1.expected.css');
+ });
+
+ it('Should place the leftovers in the beginning', function() {
+ let test = new Test(this);
+ let config = test.Comb.getConfig('csscomb');
+ config['sort-order'][0].unshift(['...']);
+ test.comb.configure(config);
+
+ return test.shouldBeEqual('leftovers-2.css', 'leftovers-2.expected.css')
+ .then(function() {
+ config['sort-order'][0].shift();
+ });
+ });
+
+ it('Should place the leftovers in the beginning of its group', function() {
+ let test = new Test(this);
+ let config = test.Comb.getConfig('csscomb');
+ config['sort-order'][1].unshift('...');
+ test.comb.configure(config);
+ return test.shouldBeEqual('leftovers-3.css', 'leftovers-3.expected.css')
+ .then(function() {
+ config['sort-order'][1].shift();
+ });
+ });
+
+ it('Should place the leftovers in the middle of its group', function() {
+ let test = new Test(this);
+ let config = test.Comb.getConfig('csscomb');
+ config['sort-order'][1].splice(1, 0, '...');
+ test.comb.configure(config);
+ return test.shouldBeEqual('leftovers-4.css', 'leftovers-4.expected.css')
+ .then(function() {
+ config['sort-order'][1].splice(1, 1);
+ });
+ });
+
+ it('Should add declaration delimiters if they are missing', function() {
+ let test = new Test(this, {'sort-order': ['position', 'z-index']});
+ return test.shouldBeEqual('missing-delimiter.css', 'missing-delimiter.expected.css');
+ });
+ });
+
+ describe('less', function() {
+ it('Should sort properties inside rules', function() {
+ let test = new Test(this, {
+ 'sort-order': [['top', 'color']]
+ });
+ return test.shouldBeEqual('rule.less', 'rule.expected.less');
+ });
+
+ it('Should sort properties inside nested rules', function() {
+ let test = new Test(this, {
+ 'sort-order': [['top', 'color']]
+ });
+ return test.shouldBeEqual('nested-rule-1.less', 'nested-rule-1.expected.less');
+ });
+
+ it('Should sort properties divided by nested rules', function() {
+ let test = new Test(this, {
+ 'sort-order': [['top', 'left', 'color']]
+ });
+ return test.shouldBeEqual('nested-rule-2.less', 'nested-rule-2.expected.less');
+ });
+
+ it('Should group declarations with proper comments and spaces (single line)', function() {
+ let test = new Test(this, {
+ 'sort-order': [['top', 'color']]
+ });
+ return test.shouldBeEqual('comments-1.less', 'comments-1.expected.less');
+ });
+
+ it('Should group declarations with proper comments and spaces (multiple lines). Test 1', function() {
+ let test = new Test(this, {
+ 'sort-order': [['top', 'color']]
+ });
+ return test.shouldBeEqual('comments-2.less', 'comments-2.expected.less');
+ });
+
+ it('Should group declarations with proper comments and spaces (multiple lines). Test 2', function() {
+ let test = new Test(this, {
+ 'sort-order': [['$variable', 'color']]
+ });
+ return test.shouldBeEqual('comments-3.less', 'comments-3.expected.less');
+ });
+
+ it('Should group declarations with proper comments and spaces (multiple lines). Test 3', function() {
+ let test = new Test(this, {
+ 'sort-order': [['$variable', 'color']]
+ });
+ return test.shouldBeEqual('comments-3.less', 'comments-3.expected.less');
+ });
+
+ it('Should divide properties from different groups with an empty line', function() {
+ let test = new Test(this, {
+ 'sort-order': [['top'], ['color']]
+ });
+ return test.shouldBeEqual('different-groups.less', 'different-groups.expected.less');
+ });
+
+ it('Should sort variables', function() {
+ let test = new Test(this, {
+ 'sort-order': [['$variable', 'color']]
+ });
+ return test.shouldBeEqual('variable.less', 'variable.expected.less');
+ });
+
+ it('Should sort imports', function() {
+ let test = new Test(this, {
+ 'sort-order': [['$import', 'color']]
+ });
+ return test.shouldBeEqual('import.less', 'import.expected.less');
+ });
+
+ it('Should sort included mixins. Test 1', function() {
+ let test = new Test(this, {
+ 'sort-order': [['$include', 'color', 'border-top', 'border-bottom']]
+ });
+ return test.shouldBeEqual('mixin-1.less', 'mixin-1.expected.less');
+ });
+
+ it('Should sort included mixins. Test 2', function() {
+ let test = new Test(this, {
+ 'sort-order': [['$include', 'top', 'color']]
+ });
+ return test.shouldBeEqual('mixin-2.less', 'mixin-2.expected.less');
+ });
+
+ it('Should sort included mixins. Test 3', function() {
+ let test = new Test(this, {
+ 'sort-order': [['$include', 'border', 'color']]
+ });
+ return test.shouldBeEqual('mixin-3.less', 'mixin-3.expected.less');
+ });
+
+ it('Should sort included mixins with specified name. Test 4', function() {
+ let test = new Test(this, {
+ 'sort-order': [['$include'], ['color'], ['$include media']]
+ });
+ return test.shouldBeEqual('mixin-4.less', 'mixin-4.expected.less');
+ });
+
+ it('Should sort @extend-s', function() {
+ let test = new Test(this, {
+ 'sort-order': [['$extend', 'color']]
+ });
+ return test.shouldBeEqual('extend.less', 'extend.expected.less');
+ });
+ });
+
+ describe('sass', function() {
+ it('Should sort properties inside rules', function() {
+ let test = new Test(this, {
+ 'sort-order': [['top', 'color']]
+ });
+ return test.shouldBeEqual('rule.sass', 'rule.expected.sass');
+ });
+
+ it('Should sort properties inside nested rules', function() {
+ let test = new Test(this, {
+ 'sort-order': [['top', 'color']]
+ });
+ return test.shouldBeEqual('nested-rule-1.sass', 'nested-rule-1.expected.sass');
+ });
+
+ it('Should sort properties divided by nested rules', function() {
+ let test = new Test(this, {
+ 'sort-order': [['top', 'left', 'color']]
+ });
+ return test.shouldBeEqual('nested-rule-2.sass', 'nested-rule-2.expected.sass');
+ });
+
+ it('Should group declarations with proper comments and spaces (multiple lines)', function() {
+ let test = new Test(this, {
+ 'sort-order': [['top', 'color']]
+ });
+ return test.shouldBeEqual('comments.sass', 'comments.expected.sass');
+ });
+
+ it('Should divide properties from different groups with an empty line', function() {
+ let test = new Test(this, {
+ 'sort-order': [['top'], ['color']]
+ });
+ return test.shouldBeEqual('different-groups.sass', 'different-groups.expected.sass');
+ });
+
+ it('Should sort variables', function() {
+ let test = new Test(this, {
+ 'sort-order': [['$variable', 'color']]
+ });
+ return test.shouldBeEqual('variable.sass', 'variable.expected.sass');
+ });
+
+ it('Should sort imports', function() {
+ let test = new Test(this, {
+ 'sort-order': [['$import', 'color']]
+ });
+ return test.shouldBeEqual('import.sass', 'import.expected.sass');
+ });
+
+ it('Should sort @include-s', function() {
+ let test = new Test(this, {
+ 'sort-order': [['$include', 'color']]
+ });
+ return test.shouldBeEqual('include.sass', 'include.expected.sass');
+ });
+
+ it('Should sort @extend-s', function() {
+ let test = new Test(this, {
+ 'sort-order': [['$extend', 'color']]
+ });
+ return test.shouldBeEqual('extend.sass', 'extend.expected.sass');
+ });
+
+ it('Should sort @include-s with specified name. Test 1', function() {
+ let test = new Test(this, {
+ 'sort-order': [['$include'], ['color'], ['$include media']]
+ });
+ return test.shouldBeEqual('include-specified-1.sass', 'include-specified-1.expected.sass');
+ });
+
+ it('Should sort @include-s with specified name. Test 2', function() {
+ let test = new Test(this, {
+ 'sort-order': [['$include'], ['color'], ['$include media']]
+ });
+ return test.shouldBeEqual('include-specified-2.sass', 'include-specified-2.expected.sass');
+ });
+
+ it('Should sort properties inside blocks passed to mixins', function() {
+ let test = new Test(this, {
+ 'sort-order': [['top', 'color']]
+ });
+ return test.shouldBeEqual('mixin.sass', 'mixin.expected.sass');
+ });
+
+ it('Should handle properties preceeding rulesets', function() {
+ let test = new Test(this, {
+ 'sort-order': [['top', 'left', 'color']]
+ });
+ return test.shouldBeEqual('ruleset.sass', 'ruleset.expected.sass');
+ });
+
+ it('Should handle properties preceeding conditions', function() {
+ let test = new Test(this, {
+ 'sort-order': [['font-size', 'display', 'top', 'color']]
+ });
+ return test.shouldBeEqual('condition.sass', 'condition.expected.sass');
+ });
+
+ it('Issue 332', function() {
+ let test = new Test(this, {
+ 'sort-order': [['color'], ['$include']]
+ });
+ return test.shouldBeEqual('issue-332.sass', 'issue-332.expected.sass');
+ });
+
+ it('Issue 332, test 2', function() {
+ let test = new Test(this, {
+ 'sort-order': [['...']],
+ 'sort-order-fallback': 'abc'
+ });
+ return test.shouldBeEqual('issue-332-2.sass', 'issue-332-2.expected.sass');
+ });
+ });
+
+ describe('scss', function() {
+ it('Should sort properties inside rules (single line)', function() {
+ let test = new Test(this, {
+ 'sort-order': [['top', 'color']]
+ });
+ return test.shouldBeEqual('rule.scss', 'rule.expected.scss');
+ });
+
+ it('Should sort properties inside rules (multiple lines)', function() {
+ let test = new Test(this, {
+ 'sort-order': [['top', 'color']]
+ });
+ return test.shouldBeEqual('rule.scss', 'rule.expected.scss');
+ });
+
+ it('Should sort properties inside nested rules', function() {
+ let test = new Test(this, {
+ 'sort-order': [['top', 'color']]
+ });
+ return test.shouldBeEqual('nested-rule-1.scss', 'nested-rule-1.expected.scss');
+ });
+
+ it('Should sort properties divided by nested rules', function() {
+ let test = new Test(this, {
+ 'sort-order': [['top', 'left', 'color']]
+ });
+ return test.shouldBeEqual('nested-rule-2.scss', 'nested-rule-2.expected.scss');
+ });
+
+ it('Should group declarations with proper comments and spaces (multiple lines)', function() {
+ let test = new Test(this, {
+ 'sort-order': [['top', 'color']]
+ });
+ return test.shouldBeEqual('comments-1.scss', 'comments-1.expected.scss');
+ });
+
+ it('Should group declarations with proper comments and spaces (single line)', function() {
+ let test = new Test(this, {
+ 'sort-order': [['top', 'color']]
+ });
+ return test.shouldBeEqual('comments-2.scss', 'comments-2.expected.scss');
+ });
+
+ it('Should divide properties from different groups with an empty line', function() {
+ let test = new Test(this, {
+ 'sort-order': [['top'], ['color']]
+ });
+ return test.shouldBeEqual('different-groups.scss', 'different-groups.expected.scss');
+ });
+
+ it('Should sort variables', function() {
+ let test = new Test(this, {
+ 'sort-order': [['$variable', 'color']]
+ });
+ return test.shouldBeEqual('variable.scss', 'variable.expected.scss');
+ });
+
+ it('Should sort imports', function() {
+ let test = new Test(this, {
+ 'sort-order': [['$import', 'color']]
+ });
+ return test.shouldBeEqual('import.scss', 'import.expected.scss');
+ });
+
+ it('Should sort @include-s', function() {
+ let test = new Test(this, {
+ 'sort-order': [['$include', 'color']]
+ });
+ return test.shouldBeEqual('include.scss', 'include.expected.scss');
+ });
+
+ it('Should sort @include-s with specified name', function() {
+ let test = new Test(this, {
+ 'sort-order': [['$include'], ['color'], ['$include media']]
+ });
+ return test.shouldBeEqual('include-specified.scss', 'include-specified.expected.scss');
+ });
+
+ it('Should sort @extend-s', function() {
+ let test = new Test(this, {
+ 'sort-order': [['$extend', 'color']]
+ });
+ return test.shouldBeEqual('extend.scss', 'extend.expected.scss');
+ });
+
+ it('Should sort properties inside blocks passed to mixins', function() {
+ let test = new Test(this, {
+ 'sort-order': [['top', 'color']]
+ });
+ return test.shouldBeEqual('mixin.scss', 'mixin.expected.scss');
+ });
+
+ it('Should handle properties preceeding rulesets', function() {
+ let test = new Test(this, {
+ 'sort-order': [['top', 'left', 'color']]
+ });
+ return test.shouldBeEqual('ruleset.scss', 'ruleset.expected.scss');
+ });
+
+ it('Should handle properties preceeding conditions', function() {
+ let test = new Test(this, {
+ 'sort-order': [['font-size', 'display', 'top', 'color']]
+ });
+ return test.shouldBeEqual('condition.scss', 'condition.expected.scss');
+ });
+
+ it('Should sort complex case with leftovers', function() {
+ let test = new Test(this, {
+ 'sort-order': [
+ ['$variable'],
+ ['position'],
+ ['...', 'border'],
+ ['$include'],
+ ['font']
+ ]
+ });
+ return test.shouldBeEqual('leftovers.scss', 'leftovers.expected.scss');
+ });
+
+ it('Issue 317', function() {
+ let test = new Test(this, {'sort-order': ['...']});
+ return test.shouldBeEqual('issue-317.scss');
+ });
+
+ it('Issue 333', function() {
+ let test = new Test(this, {'sort-order': ['...']});
+ return test.shouldBeEqual('issue-333.scss');
+ });
+
+ it('Issue 399', function() {
+ let test = new Test(this, {'sort-order': [['$extend', 'color']]});
+ return test.shouldBeEqual('issue-399.expected.scss');
+ });
+ });
+});
diff --git a/test/options/space-after-colon/detect/test.js b/test/options/space-after-colon/detect/test.js
new file mode 100644
index 00000000..5f325223
--- /dev/null
+++ b/test/options/space-after-colon/detect/test.js
@@ -0,0 +1,43 @@
+let Test = require('../../option_test');
+
+describe('Option `space-after-colon`, detect', function() {
+ describe('css', function() {
+ it('Should detect no whitespaces', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['space-after-colon'],
+ 'a { color:red }',
+ {'space-after-colon': ''}
+ );
+ });
+
+ it('Should detect space from two variants', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['space-after-colon'],
+ 'a { color: red; color :red }',
+ {'space-after-colon': ' '}
+ );
+ });
+
+ it('Should detect no whitespaces along three variants', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['space-after-colon'],
+ 'a { color: red; background :red } b { width:10px }',
+ {'space-after-colon': ''}
+ );
+ });
+
+ it('Should detect space', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['space-after-colon'],
+ 'a { color : red; background :red } b { width: 10px }',
+ {'space-after-colon': ' '}
+ );
+ });
+ });
+});
+
+
diff --git a/test/options/space-after-colon/lint/test.js b/test/options/space-after-colon/lint/test.js
new file mode 100644
index 00000000..e69de29b
diff --git a/test/options/space-after-colon/process/css/test-2.expected.css b/test/options/space-after-colon/process/css/test-2.expected.css
new file mode 100644
index 00000000..c1355f35
--- /dev/null
+++ b/test/options/space-after-colon/process/css/test-2.expected.css
@@ -0,0 +1,5 @@
+a { color: red }
+a{color: red}
+a {color : red}
+a {color : /* foo */ red }
+a {color /* bar */ : red }
diff --git a/test/options/space-after-colon/process/css/test-3.expected.css b/test/options/space-after-colon/process/css/test-3.expected.css
new file mode 100644
index 00000000..63d57dc7
--- /dev/null
+++ b/test/options/space-after-colon/process/css/test-3.expected.css
@@ -0,0 +1,10 @@
+a { color:
+ red }
+a{color:
+ red}
+a {color :
+ red}
+a {color :
+ /* foo */ red }
+a {color /* bar */ :
+ red }
diff --git a/test/options/space-after-colon/process/css/test.css b/test/options/space-after-colon/process/css/test.css
new file mode 100644
index 00000000..637656fb
--- /dev/null
+++ b/test/options/space-after-colon/process/css/test.css
@@ -0,0 +1,5 @@
+a { color: red }
+a{color:red}
+a {color : red}
+a {color : /* foo */ red }
+a {color /* bar */ : red }
diff --git a/test/options/space-after-colon/process/css/test.expected.css b/test/options/space-after-colon/process/css/test.expected.css
new file mode 100644
index 00000000..2d137811
--- /dev/null
+++ b/test/options/space-after-colon/process/css/test.expected.css
@@ -0,0 +1,5 @@
+a { color:red }
+a{color:red}
+a {color :red}
+a {color :/* foo */ red }
+a {color /* bar */ :red }
diff --git a/test/options/space-after-colon/process/sass/colon-after-property-name.expected.sass b/test/options/space-after-colon/process/sass/colon-after-property-name.expected.sass
new file mode 100644
index 00000000..9b55f4af
--- /dev/null
+++ b/test/options/space-after-colon/process/sass/colon-after-property-name.expected.sass
@@ -0,0 +1,3 @@
+a
+ color: panda
+ top: 0
diff --git a/test/options/space-after-colon/process/sass/colon-after-property-name.sass b/test/options/space-after-colon/process/sass/colon-after-property-name.sass
new file mode 100644
index 00000000..5e4e236e
--- /dev/null
+++ b/test/options/space-after-colon/process/sass/colon-after-property-name.sass
@@ -0,0 +1,3 @@
+a
+ color: panda
+ top:0
diff --git a/test/options/space-after-colon/process/sass/colon-before-property-name.expected.sass b/test/options/space-after-colon/process/sass/colon-before-property-name.expected.sass
new file mode 100644
index 00000000..a8a5e3e4
--- /dev/null
+++ b/test/options/space-after-colon/process/sass/colon-before-property-name.expected.sass
@@ -0,0 +1,3 @@
+a
+ :color panda
+ :top 0
diff --git a/test/options/space-after-colon/process/sass/colon-before-property-name.sass b/test/options/space-after-colon/process/sass/colon-before-property-name.sass
new file mode 100644
index 00000000..a8a5e3e4
--- /dev/null
+++ b/test/options/space-after-colon/process/sass/colon-before-property-name.sass
@@ -0,0 +1,3 @@
+a
+ :color panda
+ :top 0
diff --git a/test/options/space-after-colon/process/scss/pseudo-elements.expected.scss b/test/options/space-after-colon/process/scss/pseudo-elements.expected.scss
new file mode 100644
index 00000000..46b16d28
--- /dev/null
+++ b/test/options/space-after-colon/process/scss/pseudo-elements.expected.scss
@@ -0,0 +1,11 @@
+li{
+ float: left;
+
+ a:hover{
+ text-decoration: underline;
+ }
+}
+
+li:before {
+ color: tomato;
+}
diff --git a/test/options/space-after-colon/process/scss/pseudo-elements.scss b/test/options/space-after-colon/process/scss/pseudo-elements.scss
new file mode 100644
index 00000000..dbb4acea
--- /dev/null
+++ b/test/options/space-after-colon/process/scss/pseudo-elements.scss
@@ -0,0 +1,11 @@
+li{
+ float:left;
+
+ a:hover{
+ text-decoration:underline;
+ }
+}
+
+li:before {
+ color:tomato;
+}
diff --git a/test/options/space-after-colon/process/test.js b/test/options/space-after-colon/process/test.js
new file mode 100644
index 00000000..84119b16
--- /dev/null
+++ b/test/options/space-after-colon/process/test.js
@@ -0,0 +1,55 @@
+let Test = require('../../option_test');
+
+describe('Option `space-after-colon`, process', function() {
+ describe('css', function() {
+ it('Array value => should not change anything', function() {
+ let test = new Test(this, {'space-after-colon': ['', ' ']});
+ return test.shouldBeEqual('test.css');
+ });
+
+ it('Invalid string value => should not change anything', function() {
+ let test = new Test(this, {'space-after-colon': ' nani '});
+ return test.shouldBeEqual('test.css');
+ });
+
+ it('Float number value => should not change anything', function() {
+ let test = new Test(this, {'space-after-colon': 3.5});
+ return test.shouldBeEqual('test.css');
+ });
+
+ it('Integer value => should set proper space after colon', function() {
+ let test = new Test(this, {'space-after-colon': 0});
+ return test.shouldBeEqual('test.css', 'test.expected.css');
+ });
+
+ it('Valid string value (spaces only)=> should set proper space after colon', function() {
+ let test = new Test(this, {'space-after-colon': ' '});
+ return test.shouldBeEqual('test.css', 'test-2.expected.css');
+ });
+
+ it('Valid string value (spacesand newlines)=> should set proper space after colon', function() {
+ let test = new Test(this, {'space-after-colon': '\n '});
+ return test.shouldBeEqual('test.css', 'test-3.expected.css');
+ });
+ });
+
+ describe('sass', function() {
+ it('Should set proper space if colon is after property name', function() {
+ let test = new Test(this, {'space-after-colon': 2});
+ return test.shouldBeEqual('colon-after-property-name.sass', 'colon-after-property-name.expected.sass');
+ });
+
+ it('Should not change space after colon which is before property name', function() {
+ let test = new Test(this, {'space-after-colon': 1});
+ return test.shouldBeEqual('colon-before-property-name.sass', 'colon-before-property-name.expected.sass');
+ });
+ });
+
+ describe('scss', function() {
+ it('Space after colon should not affect pseudo elements', function() {
+ let test = new Test(this, {'space-after-colon': 1});
+ return test.shouldBeEqual('pseudo-elements.scss', 'pseudo-elements.expected.scss');
+ });
+ });
+});
+
diff --git a/test/options/space-after-combinator/detect/test.js b/test/options/space-after-combinator/detect/test.js
new file mode 100644
index 00000000..6728f664
--- /dev/null
+++ b/test/options/space-after-combinator/detect/test.js
@@ -0,0 +1,61 @@
+let Test = require('../../option_test');
+
+describe('Option `space-after-combinator`, detect', function() {
+ describe('css', function() {
+ it('Should detect no whitespaces after combinator', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['space-after-combinator'],
+ 'a+b { color:red }',
+ {'space-after-combinator': ''}
+ );
+ });
+
+ it('Should detect a space after combinator', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['space-after-combinator'],
+ 'a + \n b { color:red }',
+ {'space-after-combinator': ' \n '}
+ );
+ });
+
+ it('Should detect a space after combinator in long selector', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['space-after-combinator'],
+ 'a + b ~ c>d { color:red }',
+ {'space-after-combinator': ' '}
+ );
+ });
+
+ it('Should detect a space after combinator in long selector, test 2', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['space-after-combinator'],
+ 'a>b + c + d { color:red }',
+ {'space-after-combinator': ' '}
+ );
+ });
+
+ it('Should detect no whitespaces after combinator in long selector', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['space-after-combinator'],
+ 'a+b ~ c+d { color:red }',
+ {'space-after-combinator': ''}
+ );
+ });
+
+ it('Shouldn’t detect whitespaces after combinator in selector without combinators', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['space-after-combinator'],
+ 'a { color:red }',
+ {}
+ );
+ });
+ });
+});
+
+
diff --git a/test/options/space-after-combinator/lint/test.js b/test/options/space-after-combinator/lint/test.js
new file mode 100644
index 00000000..e69de29b
diff --git a/test/options/space-after-combinator/process/css/test-2.expected.css b/test/options/space-after-combinator/process/css/test-2.expected.css
new file mode 100644
index 00000000..32df4395
--- /dev/null
+++ b/test/options/space-after-combinator/process/css/test-2.expected.css
@@ -0,0 +1,10 @@
+a> b { color: red }
+a> b { color: red }
+a > b { color: red }
+a+ b { color: red }
+a+ b { color: red }
+a + b { color: red }
+a~ b { color: red }
+a~ b { color: red }
+a ~ b { color: red }
+a ~ b+ c> d { color: red }
diff --git a/test/options/space-after-combinator/process/css/test-3.expected.css b/test/options/space-after-combinator/process/css/test-3.expected.css
new file mode 100644
index 00000000..9d34666a
--- /dev/null
+++ b/test/options/space-after-combinator/process/css/test-3.expected.css
@@ -0,0 +1,22 @@
+a>
+ b { color: red }
+a>
+ b { color: red }
+a >
+ b { color: red }
+a+
+ b { color: red }
+a+
+ b { color: red }
+a +
+ b { color: red }
+a~
+ b { color: red }
+a~
+ b { color: red }
+a ~
+ b { color: red }
+a ~
+ b+
+ c>
+ d { color: red }
diff --git a/test/options/space-after-combinator/process/css/test.css b/test/options/space-after-combinator/process/css/test.css
new file mode 100644
index 00000000..4ad8d86e
--- /dev/null
+++ b/test/options/space-after-combinator/process/css/test.css
@@ -0,0 +1,10 @@
+a>b { color: red }
+a> b { color: red }
+a >b { color: red }
+a+b { color: red }
+a+ b { color: red }
+a +b { color: red }
+a~b { color: red }
+a~ b { color: red }
+a ~b { color: red }
+a ~b+ c>d { color: red }
diff --git a/test/options/space-after-combinator/process/css/test.expected.css b/test/options/space-after-combinator/process/css/test.expected.css
new file mode 100644
index 00000000..186958d1
--- /dev/null
+++ b/test/options/space-after-combinator/process/css/test.expected.css
@@ -0,0 +1,10 @@
+a>b { color: red }
+a>b { color: red }
+a >b { color: red }
+a+b { color: red }
+a+b { color: red }
+a +b { color: red }
+a~b { color: red }
+a~b { color: red }
+a ~b { color: red }
+a ~b+c>d { color: red }
diff --git a/test/options/space-after-combinator/process/test.js b/test/options/space-after-combinator/process/test.js
new file mode 100644
index 00000000..8af41c68
--- /dev/null
+++ b/test/options/space-after-combinator/process/test.js
@@ -0,0 +1,35 @@
+let Test = require('../../option_test');
+
+describe('Option `space-after-combinator`, process', function() {
+ describe('css', function() {
+ it('Array value => should not change anything', function() {
+ let test = new Test(this, {'space-after-combinator': ['', ' ']});
+ return test.shouldBeEqual('test.css');
+ });
+
+ it('Invalid string value => should not change anything', function() {
+ let test = new Test(this, {'space-after-combinator': ' nani '});
+ return test.shouldBeEqual('test.css');
+ });
+
+ it('Float number value => should not change anything', function() {
+ let test = new Test(this, {'space-after-combinator': 3.5});
+ return test.shouldBeEqual('test.css');
+ });
+
+ it('Integer value => should set proper space after combinator', function() {
+ let test = new Test(this, {'space-after-combinator': 0});
+ return test.shouldBeEqual('test.css', 'test.expected.css');
+ });
+
+ it('Valid string value (spaces only) => should set proper space after combinator', function() {
+ let test = new Test(this, {'space-after-combinator': ' '});
+ return test.shouldBeEqual('test.css', 'test-2.expected.css');
+ });
+
+ it('Valid string value (spaces and newlines) => should set proper space after combinator', function() {
+ let test = new Test(this, {'space-after-combinator': '\n '});
+ return test.shouldBeEqual('test.css', 'test-3.expected.css');
+ });
+ });
+});
diff --git a/test/options/space-after-opening-brace/detect/test.js b/test/options/space-after-opening-brace/detect/test.js
new file mode 100644
index 00000000..533cc965
--- /dev/null
+++ b/test/options/space-after-opening-brace/detect/test.js
@@ -0,0 +1,59 @@
+let Test = require('../../option_test');
+
+describe('Option `space-after-opening-brace`, detect', function() {
+ describe('css', function() {
+ it('Should detect no whitespace', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['space-after-opening-brace'],
+ 'a{top:0}',
+ {'space-after-opening-brace': ''}
+ );
+ });
+
+ it('Should detect whitespace', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['space-after-opening-brace'],
+ 'a{\n\ttop:0}',
+ {'space-after-opening-brace': '\n\t'}
+ );
+ });
+
+ it('Should detect no whitespace (2 blocks)', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['space-after-opening-brace'],
+ 'a{top:0} b{\n left:0}',
+ {'space-after-opening-brace': ''}
+ );
+ });
+
+ it('Should detect whitespace (2 blocks)', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['space-after-opening-brace'],
+ 'a{ top:0 } b{left:0}',
+ {'space-after-opening-brace': ' '}
+ );
+ });
+
+ it('Should detect no whitespace (3 blocks)', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['space-after-opening-brace'],
+ 'a{top:0} b { left: 0 } c{\n\tright:0}',
+ {'space-after-opening-brace': ''}
+ );
+ });
+
+ it('Should detect whitespace (3 blocks)', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['space-after-opening-brace'],
+ 'a{\ntop:0} b{\nleft:0} c{\n right:0}',
+ {'space-after-opening-brace': '\n'}
+ );
+ });
+ });
+});
diff --git a/test/options/space-after-opening-brace/lint/test.js b/test/options/space-after-opening-brace/lint/test.js
new file mode 100644
index 00000000..e69de29b
diff --git a/test/options/space-after-opening-brace/process/css/issue-387.css b/test/options/space-after-opening-brace/process/css/issue-387.css
new file mode 100644
index 00000000..3b66e1f1
--- /dev/null
+++ b/test/options/space-after-opening-brace/process/css/issue-387.css
@@ -0,0 +1 @@
+@media only screen (max-width:479px) {}
diff --git a/test/options/space-after-opening-brace/process/css/issue-387.expected.css b/test/options/space-after-opening-brace/process/css/issue-387.expected.css
new file mode 100644
index 00000000..aecde5a6
--- /dev/null
+++ b/test/options/space-after-opening-brace/process/css/issue-387.expected.css
@@ -0,0 +1,2 @@
+@media only screen (max-width:479px) {
+}
diff --git a/test/options/space-after-opening-brace/process/css/test-2.expected.css b/test/options/space-after-opening-brace/process/css/test-2.expected.css
new file mode 100644
index 00000000..a34ef363
--- /dev/null
+++ b/test/options/space-after-opening-brace/process/css/test-2.expected.css
@@ -0,0 +1,5 @@
+a{ top: 0}
+a { top: 0 }
+
+@media print{ a{ top: 0}}
+@media print { a { top: 0 } }
diff --git a/test/options/space-after-opening-brace/process/css/test-3.expected.css b/test/options/space-after-opening-brace/process/css/test-3.expected.css
new file mode 100644
index 00000000..4c044b43
--- /dev/null
+++ b/test/options/space-after-opening-brace/process/css/test-3.expected.css
@@ -0,0 +1,11 @@
+a{
+ top: 0}
+a {
+ top: 0 }
+
+@media print{
+ a{
+ top: 0}}
+@media print {
+ a {
+ top: 0 } }
diff --git a/test/options/space-after-opening-brace/process/css/test.css b/test/options/space-after-opening-brace/process/css/test.css
new file mode 100644
index 00000000..7c71281c
--- /dev/null
+++ b/test/options/space-after-opening-brace/process/css/test.css
@@ -0,0 +1,5 @@
+a{top: 0}
+a { top: 0 }
+
+@media print{a{top: 0}}
+@media print { a { top: 0 } }
diff --git a/test/options/space-after-opening-brace/process/css/test.expected.css b/test/options/space-after-opening-brace/process/css/test.expected.css
new file mode 100644
index 00000000..fa761a52
--- /dev/null
+++ b/test/options/space-after-opening-brace/process/css/test.expected.css
@@ -0,0 +1,5 @@
+a{top: 0}
+a {top: 0 }
+
+@media print{a{top: 0}}
+@media print {a {top: 0 } }
diff --git a/test/options/space-after-opening-brace/process/test.js b/test/options/space-after-opening-brace/process/test.js
new file mode 100644
index 00000000..c65c6841
--- /dev/null
+++ b/test/options/space-after-opening-brace/process/test.js
@@ -0,0 +1,41 @@
+let Test = require('../../option_test');
+
+describe('Option `space-after-opening-brace`, process', function() {
+ describe('css', function() {
+ it('Array value => should not change anything', function() {
+ let test = new Test(this, {'space-after-opening-brace': ['', ' ']});
+ return test.shouldBeEqual('test.css');
+ });
+
+ it('Invalid string value => should not change anything', function() {
+ let test = new Test(this, {'space-after-opening-brace': ' nani '});
+ return test.shouldBeEqual('test.css');
+ });
+
+ it('Float number value => should not change anything', function() {
+ let test = new Test(this, {'space-after-opening-brace': 3.5});
+ return test.shouldBeEqual('test.css');
+ });
+
+ it('Integer value => should set proper space after {', function() {
+ let test = new Test(this, {'space-after-opening-brace': 0});
+ return test.shouldBeEqual('test.css', 'test.expected.css');
+ });
+
+ it('Valid string value (spaces only) => should set proper space after {', function() {
+ let test = new Test(this, {'space-after-opening-brace': ' '});
+ return test.shouldBeEqual('test.css', 'test-2.expected.css');
+ });
+
+ it('Valid string value (spaces and newlines) => should set proper space after {', function() {
+ let test = new Test(this, {'space-after-opening-brace': '\n '});
+ return test.shouldBeEqual('test.css', 'test-3.expected.css');
+ });
+
+ it('Issue 387', function() {
+ let test = new Test(this, {'space-after-opening-brace': '\n'});
+ return test.shouldBeEqual('issue-387.css', 'issue-387.expected.css');
+ });
+ });
+});
+
diff --git a/test/options/space-after-selector-delimiter/detect/test.js b/test/options/space-after-selector-delimiter/detect/test.js
new file mode 100644
index 00000000..228942f1
--- /dev/null
+++ b/test/options/space-after-selector-delimiter/detect/test.js
@@ -0,0 +1,59 @@
+let Test = require('../../option_test');
+
+describe('Option `space-after-selector-delimiter`, detect', function() {
+ describe('css', function() {
+ it('Should detect no whitespace', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['space-after-selector-delimiter'],
+ 'a,b{top:0}',
+ {'space-after-selector-delimiter': ''}
+ );
+ });
+
+ it('Should detect whitespace', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['space-after-selector-delimiter'],
+ 'a, \n b {top:0}',
+ {'space-after-selector-delimiter': ' \n '}
+ );
+ });
+
+ it('Should detect no whitespace (2 blocks)', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['space-after-selector-delimiter'],
+ 'a,b{top:0} a, b{left:0}',
+ {'space-after-selector-delimiter': ''}
+ );
+ });
+
+ it('Should detect whitespace (2 blocks)', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['space-after-selector-delimiter'],
+ 'a, b {top:0} b,a{left:0}',
+ {'space-after-selector-delimiter': ' '}
+ );
+ });
+
+ it('Should detect no whitespace (3 blocks)', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['space-after-selector-delimiter'],
+ 'a, b{top:0} b,c{left:0} c,d{right:0}',
+ {'space-after-selector-delimiter': ''}
+ );
+ });
+
+ it('Should detect whitespace (3 blocks)', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['space-after-selector-delimiter'],
+ 'a,b{top:0} b, c{left:0} c, sd{right:0}',
+ {'space-after-selector-delimiter': ' '}
+ );
+ });
+ });
+});
diff --git a/test/options/space-after-selector-delimiter/lint/test.js b/test/options/space-after-selector-delimiter/lint/test.js
new file mode 100644
index 00000000..e69de29b
diff --git a/test/options/space-after-selector-delimiter/process/css/test-2.expected.css b/test/options/space-after-selector-delimiter/process/css/test-2.expected.css
new file mode 100644
index 00000000..92d1ece8
--- /dev/null
+++ b/test/options/space-after-selector-delimiter/process/css/test-2.expected.css
@@ -0,0 +1,5 @@
+a, b { color: red }
+a, b { color: red }
+a , b { color: red }
+a, b { color: red }
+a+b, c>d, e{ color: red }
\ No newline at end of file
diff --git a/test/options/space-after-selector-delimiter/process/css/test-3.expected.css b/test/options/space-after-selector-delimiter/process/css/test-3.expected.css
new file mode 100644
index 00000000..6a198487
--- /dev/null
+++ b/test/options/space-after-selector-delimiter/process/css/test-3.expected.css
@@ -0,0 +1,11 @@
+a,
+ b { color: red }
+a,
+ b { color: red }
+a ,
+ b { color: red }
+a,
+ b { color: red }
+a+b,
+ c>d,
+ e{ color: red }
\ No newline at end of file
diff --git a/test/options/space-after-selector-delimiter/process/css/test.css b/test/options/space-after-selector-delimiter/process/css/test.css
new file mode 100644
index 00000000..cd8e7d77
--- /dev/null
+++ b/test/options/space-after-selector-delimiter/process/css/test.css
@@ -0,0 +1,6 @@
+a,b { color: red }
+a, b { color: red }
+a ,b { color: red }
+a,
+b { color: red }
+a+b,c>d,e{ color: red }
\ No newline at end of file
diff --git a/test/options/space-after-selector-delimiter/process/css/test.expected.css b/test/options/space-after-selector-delimiter/process/css/test.expected.css
new file mode 100644
index 00000000..b0d4fd20
--- /dev/null
+++ b/test/options/space-after-selector-delimiter/process/css/test.expected.css
@@ -0,0 +1,5 @@
+a,b { color: red }
+a,b { color: red }
+a ,b { color: red }
+a,b { color: red }
+a+b,c>d,e{ color: red }
\ No newline at end of file
diff --git a/test/options/space-after-selector-delimiter/process/sass/issue-238.expected.sass b/test/options/space-after-selector-delimiter/process/sass/issue-238.expected.sass
new file mode 100644
index 00000000..18571608
--- /dev/null
+++ b/test/options/space-after-selector-delimiter/process/sass/issue-238.expected.sass
@@ -0,0 +1,5 @@
+html,
+body,
+p
+ background: #fff
+ color: #000
diff --git a/test/options/space-after-selector-delimiter/process/sass/issue-238.sass b/test/options/space-after-selector-delimiter/process/sass/issue-238.sass
new file mode 100644
index 00000000..5fc2e131
--- /dev/null
+++ b/test/options/space-after-selector-delimiter/process/sass/issue-238.sass
@@ -0,0 +1,4 @@
+html,
+body, p
+ background: #fff
+ color: #000
diff --git a/test/options/space-after-selector-delimiter/process/test.js b/test/options/space-after-selector-delimiter/process/test.js
new file mode 100644
index 00000000..4875c8a8
--- /dev/null
+++ b/test/options/space-after-selector-delimiter/process/test.js
@@ -0,0 +1,42 @@
+let Test = require('../../option_test');
+
+describe('Option `space-after-selector-delimiter`, process', function() {
+ describe('css', function() {
+ it('Array value => should not change anything', function() {
+ let test = new Test(this, {'space-after-selector-delimiter': ['', ' ']});
+ return test.shouldBeEqual('test.css');
+ });
+
+ it('Invalid string value => should not change anything', function() {
+ let test = new Test(this, {'space-after-selector-delimiter': ' nani '});
+ return test.shouldBeEqual('test.css');
+ });
+
+ it('Float number value => should not change anything', function() {
+ let test = new Test(this, {'space-after-selector-delimiter': 3.5});
+ return test.shouldBeEqual('test.css');
+ });
+
+ it('Integer value => should set proper space after selector delimiter', function() {
+ let test = new Test(this, {'space-after-selector-delimiter': 0});
+ return test.shouldBeEqual('test.css', 'test.expected.css');
+ });
+
+ it('Valid string value (spaces only) => should set proper space after selector delimiter', function() {
+ let test = new Test(this, {'space-after-selector-delimiter': ' '});
+ return test.shouldBeEqual('test.css', 'test-2.expected.css');
+ });
+
+ it('Valid string value (spaces and newlines) => should set proper space after selector delimiter', function() {
+ let test = new Test(this, {'space-after-selector-delimiter': '\n '});
+ return test.shouldBeEqual('test.css', 'test-3.expected.css');
+ });
+ });
+
+ describe('sass', function() {
+ it('Issue 238', function() {
+ let test = new Test(this, {'space-after-selector-delimiter': '\n'});
+ return test.shouldBeEqual('issue-238.sass', 'issue-238.expected.sass');
+ });
+ });
+});
diff --git a/test/options/space-before-closing-brace/detect/test.js b/test/options/space-before-closing-brace/detect/test.js
new file mode 100644
index 00000000..abbed180
--- /dev/null
+++ b/test/options/space-before-closing-brace/detect/test.js
@@ -0,0 +1,50 @@
+let Test = require('../../option_test');
+
+describe('Option `space-before-closing-brace`, detect', function() {
+ describe('css', function() {
+ it('Should detect no whitespace', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['space-before-closing-brace'],
+ 'a{top:0}',
+ {'space-before-closing-brace': ''}
+ );
+ });
+
+ it('Should detect no whitespace (2 blocks)', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['space-before-closing-brace'],
+ 'a{top:0} b { color: tomato; }',
+ {'space-before-closing-brace': ''}
+ );
+ });
+
+ it('Should detect whitespace', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['space-before-closing-brace'],
+ 'a { top:0 }',
+ {'space-before-closing-brace': ' '}
+ );
+ });
+
+ it('Should detect whitespace (2 blocks)', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['space-before-closing-brace'],
+ 'a { top:0 } b{color:tomato;}',
+ {'space-before-closing-brace': ' '}
+ );
+ });
+
+ it('Should detect whitespace (mix with block indent)', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['space-before-closing-brace', 'block-indent'],
+ 'a {\n top:0\n }\nb{\n color:tomato;\n }',
+ {'block-indent': ' ', 'space-before-closing-brace': '\n '}
+ );
+ });
+ });
+});
diff --git a/test/options/space-before-closing-brace/lint/test.js b/test/options/space-before-closing-brace/lint/test.js
new file mode 100644
index 00000000..e69de29b
diff --git a/test/options/space-before-closing-brace/process/css/test-2.expected.css b/test/options/space-before-closing-brace/process/css/test-2.expected.css
new file mode 100644
index 00000000..86ba2673
--- /dev/null
+++ b/test/options/space-before-closing-brace/process/css/test-2.expected.css
@@ -0,0 +1,5 @@
+a{top: 0 }
+a {top: 0 }
+
+@media print{a{top: 0 } }
+@media print { a { top: 0 } }
diff --git a/test/options/space-before-closing-brace/process/css/test-3.expected.css b/test/options/space-before-closing-brace/process/css/test-3.expected.css
new file mode 100644
index 00000000..9868eaa7
--- /dev/null
+++ b/test/options/space-before-closing-brace/process/css/test-3.expected.css
@@ -0,0 +1,11 @@
+a{top: 0
+ }
+a {top: 0
+ }
+
+@media print{a{top: 0
+ }
+ }
+@media print { a { top: 0
+ }
+ }
diff --git a/test/options/space-before-closing-brace/process/css/test.css b/test/options/space-before-closing-brace/process/css/test.css
new file mode 100644
index 00000000..2c0a59ca
--- /dev/null
+++ b/test/options/space-before-closing-brace/process/css/test.css
@@ -0,0 +1,5 @@
+a{top: 0}
+a {top: 0 }
+
+@media print{a{top: 0}}
+@media print { a { top: 0 } }
diff --git a/test/options/space-before-closing-brace/process/css/test.expected.css b/test/options/space-before-closing-brace/process/css/test.expected.css
new file mode 100644
index 00000000..9a2f6335
--- /dev/null
+++ b/test/options/space-before-closing-brace/process/css/test.expected.css
@@ -0,0 +1,5 @@
+a{top: 0}
+a {top: 0}
+
+@media print{a{top: 0}}
+@media print { a { top: 0}}
diff --git a/test/options/space-before-closing-brace/process/test.js b/test/options/space-before-closing-brace/process/test.js
new file mode 100644
index 00000000..1bc22dbf
--- /dev/null
+++ b/test/options/space-before-closing-brace/process/test.js
@@ -0,0 +1,36 @@
+let Test = require('../../option_test');
+
+describe('Option `space-before-closing-brace`, process', function() {
+ describe('css', function() {
+ it('Array value => should not change anything', function() {
+ let test = new Test(this, {'space-before-closing-brace': ['', ' ']});
+ return test.shouldBeEqual('test.css');
+ });
+
+ it('Invalid string value => should not change anything', function() {
+ let test = new Test(this, {'space-before-closing-brace': ' nani '});
+ return test.shouldBeEqual('test.css');
+ });
+
+ it('Float number value => should not change anything', function() {
+ let test = new Test(this, {'space-before-closing-brace': 3.5});
+ return test.shouldBeEqual('test.css');
+ });
+
+ it('Integer value => should set proper space before }', function() {
+ let test = new Test(this, {'space-before-closing-brace': 0});
+ return test.shouldBeEqual('test.css', 'test.expected.css');
+ });
+
+ it('Valid string value (spaces only) => should set proper space before }', function() {
+ let test = new Test(this, {'space-before-closing-brace': ' '});
+ return test.shouldBeEqual('test.css', 'test-2.expected.css');
+ });
+
+ it('Valid string value (spaces and newlines) => should set proper space before }', function() {
+ let test = new Test(this, {'space-before-closing-brace': '\n '});
+ return test.shouldBeEqual('test.css', 'test-3.expected.css');
+ });
+ });
+});
+
diff --git a/test/options/space-before-colon/detect/test.js b/test/options/space-before-colon/detect/test.js
new file mode 100644
index 00000000..7189a60a
--- /dev/null
+++ b/test/options/space-before-colon/detect/test.js
@@ -0,0 +1,43 @@
+let Test = require('../../option_test');
+
+describe('Option `space-before-colon`, detect', function() {
+ describe('css', function() {
+ it('Should detect no whitespaces', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['space-before-colon'],
+ 'a { color:red }',
+ {'space-before-colon': ''}
+ );
+ });
+
+ it('Should detect no space from two variants', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['space-before-colon'],
+ 'a { color: red; color :red }',
+ {'space-before-colon': ''}
+ );
+ });
+
+ it('Should detect no whitespaces along three variants', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['space-before-colon'],
+ 'a { color: red; background :red } b { width:10px }',
+ {'space-before-colon': ''}
+ );
+ });
+
+ it('Should detect space', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['space-before-colon'],
+ 'a { color : red; background :red } b { width:10px }',
+ {'space-before-colon': ' '}
+ );
+ });
+ });
+});
+
+
diff --git a/test/options/space-before-colon/lint/test.js b/test/options/space-before-colon/lint/test.js
new file mode 100644
index 00000000..e69de29b
diff --git a/test/options/space-before-colon/process/css/test-2.expected.css b/test/options/space-before-colon/process/css/test-2.expected.css
new file mode 100644
index 00000000..8491cff8
--- /dev/null
+++ b/test/options/space-before-colon/process/css/test-2.expected.css
@@ -0,0 +1,5 @@
+a { color : red }
+a{color :red}
+a {color : red}
+a {color : /* foo */ red }
+a {color /* bar */ : red }
diff --git a/test/options/space-before-colon/process/css/test-3.expected.css b/test/options/space-before-colon/process/css/test-3.expected.css
new file mode 100644
index 00000000..2d951a78
--- /dev/null
+++ b/test/options/space-before-colon/process/css/test-3.expected.css
@@ -0,0 +1,10 @@
+a { color
+ : red }
+a{color
+ :red}
+a {color
+ : red}
+a {color
+ : /* foo */ red }
+a {color /* bar */
+ : red }
diff --git a/test/options/space-before-colon/process/css/test.css b/test/options/space-before-colon/process/css/test.css
new file mode 100644
index 00000000..637656fb
--- /dev/null
+++ b/test/options/space-before-colon/process/css/test.css
@@ -0,0 +1,5 @@
+a { color: red }
+a{color:red}
+a {color : red}
+a {color : /* foo */ red }
+a {color /* bar */ : red }
diff --git a/test/options/space-before-colon/process/css/test.expected.css b/test/options/space-before-colon/process/css/test.expected.css
new file mode 100644
index 00000000..9ffc1c4b
--- /dev/null
+++ b/test/options/space-before-colon/process/css/test.expected.css
@@ -0,0 +1,5 @@
+a { color: red }
+a{color:red}
+a {color: red}
+a {color: /* foo */ red }
+a {color /* bar */: red }
diff --git a/test/options/space-before-colon/process/sass/test.expected.sass b/test/options/space-before-colon/process/sass/test.expected.sass
new file mode 100644
index 00000000..a61746f7
--- /dev/null
+++ b/test/options/space-before-colon/process/sass/test.expected.sass
@@ -0,0 +1,3 @@
+a
+ color : tomato
+ top : 0
diff --git a/test/options/space-before-colon/process/sass/test.sass b/test/options/space-before-colon/process/sass/test.sass
new file mode 100644
index 00000000..f38512b5
--- /dev/null
+++ b/test/options/space-before-colon/process/sass/test.sass
@@ -0,0 +1,3 @@
+a
+ color: tomato
+ top: 0
diff --git a/test/options/space-before-colon/process/sass/test2.sass b/test/options/space-before-colon/process/sass/test2.sass
new file mode 100644
index 00000000..3a5df0b2
--- /dev/null
+++ b/test/options/space-before-colon/process/sass/test2.sass
@@ -0,0 +1,3 @@
+a
+ :color tomato
+ :top 0
diff --git a/test/options/space-before-colon/process/test.js b/test/options/space-before-colon/process/test.js
new file mode 100644
index 00000000..c3b860f2
--- /dev/null
+++ b/test/options/space-before-colon/process/test.js
@@ -0,0 +1,48 @@
+let Test = require('../../option_test');
+
+describe('Option `space-before-colon`, process', function() {
+ describe('css', function() {
+ it('Array value => should not change anything', function() {
+ let test = new Test(this, {'space-before-colon': ['', ' ']});
+ return test.shouldBeEqual('test.css');
+ });
+
+ it('Invalid string value => should not change anything', function() {
+ let test = new Test(this, {'space-before-colon': ' nani '});
+ return test.shouldBeEqual('test.css');
+ });
+
+ it('Float number value => should not change anything', function() {
+ let test = new Test(this, {'space-before-colon': 3.5});
+ return test.shouldBeEqual('test.css');
+ });
+
+ it('Integer value => should set proper space before colon', function() {
+ let test = new Test(this, {'space-before-colon': 0});
+ return test.shouldBeEqual('test.css', 'test.expected.css');
+ });
+
+ it('Valid string value (spaces only) => should set proper space before colon', function() {
+ let test = new Test(this, {'space-before-colon': ' '});
+ return test.shouldBeEqual('test.css', 'test-2.expected.css');
+ });
+
+ it('Valid string value (spaces and newlines) => should set proper space before colon', function() {
+ let test = new Test(this, {'space-before-colon': '\n '});
+ return test.shouldBeEqual('test.css', 'test-3.expected.css');
+ });
+ });
+
+ describe('sass', function() {
+ it('Should correct space', function() {
+ let test = new Test(this, {'space-before-colon': 1});
+ return test.shouldBeEqual('test.sass', 'test.expected.sass');
+ });
+
+ it('Should not correct space', function() {
+ let test = new Test(this, {'space-before-colon': 1});
+ return test.shouldBeEqual('test2.sass');
+ });
+ });
+});
+
diff --git a/test/options/space-before-combinator/detect/test.js b/test/options/space-before-combinator/detect/test.js
new file mode 100644
index 00000000..e24e9a72
--- /dev/null
+++ b/test/options/space-before-combinator/detect/test.js
@@ -0,0 +1,61 @@
+let Test = require('../../option_test');
+
+describe('Option `space-before-combinator`, detect', function() {
+ describe('css', function() {
+ it('Should detect no whitespaces before combinator', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['space-before-combinator'],
+ 'a+b { color:red }',
+ {'space-before-combinator': ''}
+ );
+ });
+
+ it('Should detect a space before combinator', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['space-before-combinator'],
+ 'a + b { color:red }',
+ {'space-before-combinator': ' '}
+ );
+ });
+
+ it('Should detect a space before combinator in long selector', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['space-before-combinator'],
+ 'a + b ~ c>d { color:red }',
+ {'space-before-combinator': ' '}
+ );
+ });
+
+ it('Should detect a space before combinator in long selector, test 2', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['space-before-combinator'],
+ 'a>b + c + d { color:red }',
+ {'space-before-combinator': ' '}
+ );
+ });
+
+ it('Should detect no whitespaces before combinator in long selector', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['space-before-combinator'],
+ 'a+b ~ c+d { color:red }',
+ {'space-before-combinator': ''}
+ );
+ });
+
+ it('Shouldn’t detect whitespaces before combinator in selector without combinators', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['space-before-combinator'],
+ 'a { color:red }',
+ {}
+ );
+ });
+ });
+});
+
+
diff --git a/test/options/space-before-combinator/lint/test.js b/test/options/space-before-combinator/lint/test.js
new file mode 100644
index 00000000..e69de29b
diff --git a/test/options/space-before-combinator/process/css/test-2.expected.css b/test/options/space-before-combinator/process/css/test-2.expected.css
new file mode 100644
index 00000000..d1320b14
--- /dev/null
+++ b/test/options/space-before-combinator/process/css/test-2.expected.css
@@ -0,0 +1,10 @@
+a >b { color: red }
+a > b { color: red }
+a >b { color: red }
+a +b { color: red }
+a + b { color: red }
+a +b { color: red }
+a ~b { color: red }
+a ~ b { color: red }
+a ~b { color: red }
+a ~b + c >d { color: red }
diff --git a/test/options/space-before-combinator/process/css/test-3.expected.css b/test/options/space-before-combinator/process/css/test-3.expected.css
new file mode 100644
index 00000000..2a7f3179
--- /dev/null
+++ b/test/options/space-before-combinator/process/css/test-3.expected.css
@@ -0,0 +1,22 @@
+a
+ >b { color: red }
+a
+ > b { color: red }
+a
+ >b { color: red }
+a
+ +b { color: red }
+a
+ + b { color: red }
+a
+ +b { color: red }
+a
+ ~b { color: red }
+a
+ ~ b { color: red }
+a
+ ~b { color: red }
+a
+ ~b
+ + c
+ >d { color: red }
diff --git a/test/options/space-before-combinator/process/css/test.css b/test/options/space-before-combinator/process/css/test.css
new file mode 100644
index 00000000..4ad8d86e
--- /dev/null
+++ b/test/options/space-before-combinator/process/css/test.css
@@ -0,0 +1,10 @@
+a>b { color: red }
+a> b { color: red }
+a >b { color: red }
+a+b { color: red }
+a+ b { color: red }
+a +b { color: red }
+a~b { color: red }
+a~ b { color: red }
+a ~b { color: red }
+a ~b+ c>d { color: red }
diff --git a/test/options/space-before-combinator/process/css/test.expected.css b/test/options/space-before-combinator/process/css/test.expected.css
new file mode 100644
index 00000000..920bd510
--- /dev/null
+++ b/test/options/space-before-combinator/process/css/test.expected.css
@@ -0,0 +1,10 @@
+a>b { color: red }
+a> b { color: red }
+a>b { color: red }
+a+b { color: red }
+a+ b { color: red }
+a+b { color: red }
+a~b { color: red }
+a~ b { color: red }
+a~b { color: red }
+a~b+ c>d { color: red }
diff --git a/test/options/space-before-combinator/process/scss/test.expected.scss b/test/options/space-before-combinator/process/scss/test.expected.scss
new file mode 100644
index 00000000..d325dfaf
--- /dev/null
+++ b/test/options/space-before-combinator/process/scss/test.expected.scss
@@ -0,0 +1,15 @@
+a >b {
+ color: red;
+
+ & >c {
+ color: red;
+ }
+}
+
+a {
+ color: red;
+
+ >c {
+ color: red;
+ }
+}
diff --git a/test/options/space-before-combinator/process/scss/test.scss b/test/options/space-before-combinator/process/scss/test.scss
new file mode 100644
index 00000000..f7d1894b
--- /dev/null
+++ b/test/options/space-before-combinator/process/scss/test.scss
@@ -0,0 +1,15 @@
+a>b {
+ color: red;
+
+ &>c {
+ color: red;
+ }
+}
+
+a {
+ color: red;
+
+ >c {
+ color: red;
+ }
+}
diff --git a/test/options/space-before-combinator/process/test.js b/test/options/space-before-combinator/process/test.js
new file mode 100644
index 00000000..523f3b60
--- /dev/null
+++ b/test/options/space-before-combinator/process/test.js
@@ -0,0 +1,42 @@
+let Test = require('../../option_test');
+
+describe('Option `space-before-combinator`, process', function() {
+ describe('css', function() {
+ it('Array value => should not change anything', function() {
+ let test = new Test(this, {'space-before-combinator': ['', ' ']});
+ return test.shouldBeEqual('test.css');
+ });
+
+ it('Invalid string value => should not change anything', function() {
+ let test = new Test(this, {'space-before-combinator': ' nani '});
+ return test.shouldBeEqual('test.css');
+ });
+
+ it('Float number value => should not change anything', function() {
+ let test = new Test(this, {'space-before-combinator': 3.5});
+ return test.shouldBeEqual('test.css');
+ });
+
+ it('Integer value => should set proper space before combinator', function() {
+ let test = new Test(this, {'space-before-combinator': 0});
+ return test.shouldBeEqual('test.css', 'test.expected.css');
+ });
+
+ it('Valid string value (spaces only) => should set proper space before combinator', function() {
+ let test = new Test(this, {'space-before-combinator': ' '});
+ return test.shouldBeEqual('test.css', 'test-2.expected.css');
+ });
+
+ it('Valid string value (spaces and newlines) => should set proper space before combinator', function() {
+ let test = new Test(this, {'space-before-combinator': '\n '});
+ return test.shouldBeEqual('test.css', 'test-3.expected.css');
+ });
+ });
+
+ describe('scss', function() {
+ it('Should not touch leading combinators', function() {
+ let test = new Test(this, {'space-before-combinator': ' '});
+ return test.shouldBeEqual('test.scss', 'test.expected.scss');
+ });
+ });
+});
diff --git a/test/options/space-before-opening-brace/detect/test.js b/test/options/space-before-opening-brace/detect/test.js
new file mode 100644
index 00000000..b75bfae9
--- /dev/null
+++ b/test/options/space-before-opening-brace/detect/test.js
@@ -0,0 +1,59 @@
+let Test = require('../../option_test');
+
+describe('Option `space-before-opening-brace`, detect', function() {
+ describe('css', function() {
+ it('Should detect no whitespace', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['space-before-opening-brace'],
+ 'a{top:0}',
+ {'space-before-opening-brace': ''}
+ );
+ });
+
+ it('Should detect whitespace', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['space-before-opening-brace'],
+ 'a \n {top:0}',
+ {'space-before-opening-brace': ' \n '}
+ );
+ });
+
+ it('Should detect no whitespace (2 blocks)', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['space-before-opening-brace'],
+ 'a{top:0} b {left:0}',
+ {'space-before-opening-brace': ''}
+ );
+ });
+
+ it('Should detect whitespace (2 blocks)', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['space-before-opening-brace'],
+ 'a {top:0} b{left:0}',
+ {'space-before-opening-brace': ' '}
+ );
+ });
+
+ it('Should detect no whitespace (3 blocks)', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['space-before-opening-brace'],
+ 'a {top:0} b{left:0} c{right:0}',
+ {'space-before-opening-brace': ''}
+ );
+ });
+
+ it('Should detect whitespace (3 blocks)', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['space-before-opening-brace'],
+ 'a{top:0} b {left:0} c {right:0}',
+ {'space-before-opening-brace': ' '}
+ );
+ });
+ });
+});
diff --git a/test/options/space-before-opening-brace/lint/test.js b/test/options/space-before-opening-brace/lint/test.js
new file mode 100644
index 00000000..e69de29b
diff --git a/test/options/space-before-opening-brace/process/css/issue-232.css b/test/options/space-before-opening-brace/process/css/issue-232.css
new file mode 100644
index 00000000..bc99e743
--- /dev/null
+++ b/test/options/space-before-opening-brace/process/css/issue-232.css
@@ -0,0 +1,13 @@
+@font-face
+{
+ font-family: Panda;
+ src: url(panda.ttf);
+}
+
+@media screen and (max-width: 400px)
+{
+ @-ms-viewport
+ {
+ width: 320px;
+ }
+}
diff --git a/test/options/space-before-opening-brace/process/css/issue-232.expected.css b/test/options/space-before-opening-brace/process/css/issue-232.expected.css
new file mode 100644
index 00000000..04d76f38
--- /dev/null
+++ b/test/options/space-before-opening-brace/process/css/issue-232.expected.css
@@ -0,0 +1,10 @@
+@font-face {
+ font-family: Panda;
+ src: url(panda.ttf);
+}
+
+@media screen and (max-width: 400px) {
+ @-ms-viewport {
+ width: 320px;
+ }
+}
diff --git a/test/options/space-before-opening-brace/process/css/test-2.expected.css b/test/options/space-before-opening-brace/process/css/test-2.expected.css
new file mode 100644
index 00000000..68a688a2
--- /dev/null
+++ b/test/options/space-before-opening-brace/process/css/test-2.expected.css
@@ -0,0 +1,5 @@
+a {top: 0}
+a {top: 0}
+
+@media print {a {top: 0}}
+@media print { a { top: 0 } }
diff --git a/test/options/space-before-opening-brace/process/css/test-3.expected.css b/test/options/space-before-opening-brace/process/css/test-3.expected.css
new file mode 100644
index 00000000..8efc4e9d
--- /dev/null
+++ b/test/options/space-before-opening-brace/process/css/test-3.expected.css
@@ -0,0 +1,11 @@
+a
+ {top: 0}
+a
+ {top: 0}
+
+@media print
+ {a
+ {top: 0}}
+@media print
+ { a
+ { top: 0 } }
diff --git a/test/options/space-before-opening-brace/process/css/test.css b/test/options/space-before-opening-brace/process/css/test.css
new file mode 100644
index 00000000..0f8fadcc
--- /dev/null
+++ b/test/options/space-before-opening-brace/process/css/test.css
@@ -0,0 +1,5 @@
+a{top: 0}
+a {top: 0}
+
+@media print{a{top: 0}}
+@media print { a { top: 0 } }
diff --git a/test/options/space-before-opening-brace/process/css/test.expected.css b/test/options/space-before-opening-brace/process/css/test.expected.css
new file mode 100644
index 00000000..32014ddb
--- /dev/null
+++ b/test/options/space-before-opening-brace/process/css/test.expected.css
@@ -0,0 +1,5 @@
+a{top: 0}
+a{top: 0}
+
+@media print{a{top: 0}}
+@media print{ a{ top: 0 } }
diff --git a/test/options/space-before-opening-brace/process/scss/issue-231.expected.scss b/test/options/space-before-opening-brace/process/scss/issue-231.expected.scss
new file mode 100644
index 00000000..917eb73a
--- /dev/null
+++ b/test/options/space-before-opening-brace/process/scss/issue-231.expected.scss
@@ -0,0 +1,8 @@
+li {
+ float: left;
+ @include respond-to(mobile) {
+ float: none;
+ width: $width;
+ $width: 100%;
+ }
+}
diff --git a/test/options/space-before-opening-brace/process/scss/issue-231.scss b/test/options/space-before-opening-brace/process/scss/issue-231.scss
new file mode 100644
index 00000000..f0abf87d
--- /dev/null
+++ b/test/options/space-before-opening-brace/process/scss/issue-231.scss
@@ -0,0 +1,8 @@
+li{
+ float: left;
+ @include respond-to(mobile){
+ float: none;
+ width: $width;
+ $width: 100%;
+ }
+}
diff --git a/test/options/space-before-opening-brace/process/scss/issue-319.expected.scss b/test/options/space-before-opening-brace/process/scss/issue-319.expected.scss
new file mode 100644
index 00000000..4b0777bf
--- /dev/null
+++ b/test/options/space-before-opening-brace/process/scss/issue-319.expected.scss
@@ -0,0 +1,9 @@
+@import '../../helpers/__helpers';
+
+.funky {
+ font: {
+ family: fantasy;
+ size: 30em;
+ weight: bold;
+ }
+}
diff --git a/test/options/space-before-opening-brace/process/scss/issue-319.scss b/test/options/space-before-opening-brace/process/scss/issue-319.scss
new file mode 100644
index 00000000..068141cc
--- /dev/null
+++ b/test/options/space-before-opening-brace/process/scss/issue-319.scss
@@ -0,0 +1,9 @@
+@import '../../helpers/__helpers';
+
+.funky{
+ font:{
+ family: fantasy;
+ size: 30em;
+ weight: bold;
+ }
+}
diff --git a/test/options/space-before-opening-brace/process/test.js b/test/options/space-before-opening-brace/process/test.js
new file mode 100644
index 00000000..dbf0de74
--- /dev/null
+++ b/test/options/space-before-opening-brace/process/test.js
@@ -0,0 +1,52 @@
+let Test = require('../../option_test');
+
+describe('Option `space-before-opening-brace`, process', function() {
+ describe('css', function() {
+ it('Array value => should not change anything', function() {
+ let test = new Test(this, {'space-before-opening-brace': ['', ' ']});
+ return test.shouldBeEqual('test.css');
+ });
+
+ it('Invalid string value => should not change anything', function() {
+ let test = new Test(this, {'space-before-opening-brace': ' nani '});
+ return test.shouldBeEqual('test.css');
+ });
+
+ it('Float number value => should not change anything', function() {
+ let test = new Test(this, {'space-before-opening-brace': 3.5});
+ return test.shouldBeEqual('test.css');
+ });
+
+ it('Integer value => should set proper space before {', function() {
+ let test = new Test(this, {'space-before-opening-brace': 0});
+ return test.shouldBeEqual('test.css', 'test.expected.css');
+ });
+
+ it('Valid string value (spaces only) => should set proper space before {', function() {
+ let test = new Test(this, {'space-before-opening-brace': ' '});
+ return test.shouldBeEqual('test.css', 'test-2.expected.css');
+ });
+
+ it('Valid string value (spaces and newlines) => should set proper space before {', function() {
+ let test = new Test(this, {'space-before-opening-brace': '\n '});
+ return test.shouldBeEqual('test.css', 'test-3.expected.css');
+ });
+
+ it('Issue 232', function() {
+ let test = new Test(this, {'space-before-opening-brace': 1});
+ return test.shouldBeEqual('issue-232.css', 'issue-232.expected.css');
+ });
+ });
+
+ describe('scss', function() {
+ it('Issue 231', function() {
+ let test = new Test(this, {'space-before-opening-brace': 1});
+ return test.shouldBeEqual('issue-231.scss', 'issue-231.expected.scss');
+ });
+
+ it('Issue 319', function() {
+ let test = new Test(this, {'space-before-opening-brace': 1});
+ return test.shouldBeEqual('issue-319.scss', 'issue-319.expected.scss');
+ });
+ });
+});
diff --git a/test/options/space-before-selector-delimiter/detect/test.js b/test/options/space-before-selector-delimiter/detect/test.js
new file mode 100644
index 00000000..72f67296
--- /dev/null
+++ b/test/options/space-before-selector-delimiter/detect/test.js
@@ -0,0 +1,59 @@
+let Test = require('../../option_test');
+
+describe('Option `space-before-selector-delimiter`, detect', function() {
+ describe('css', function() {
+ it('Should detect no whitespace', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['space-before-selector-delimiter'],
+ 'a,b{top:0}',
+ {'space-before-selector-delimiter': ''}
+ );
+ });
+
+ it('Should detect whitespace', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['space-before-selector-delimiter'],
+ 'a \n ,b {top:0}',
+ {'space-before-selector-delimiter': ' \n '}
+ );
+ });
+
+ it('Should detect no whitespace (2 blocks)', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['space-before-selector-delimiter'],
+ 'a,b{top:0} a ,b{left:0}',
+ {'space-before-selector-delimiter': ''}
+ );
+ });
+
+ it('Should detect whitespace (2 blocks)', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['space-before-selector-delimiter'],
+ 'a ,b {top:0} b,a{left:0}',
+ {'space-before-selector-delimiter': ' '}
+ );
+ });
+
+ it('Should detect no whitespace (3 blocks)', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['space-before-selector-delimiter'],
+ 'a ,b{top:0} b,c{left:0} c,d{right:0}',
+ {'space-before-selector-delimiter': ''}
+ );
+ });
+
+ it('Should detect whitespace (3 blocks)', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['space-before-selector-delimiter'],
+ 'a,b{top:0} b ,c{left:0} c ,d{right:0}',
+ {'space-before-selector-delimiter': ' '}
+ );
+ });
+ });
+});
diff --git a/test/options/space-before-selector-delimiter/lint/test.js b/test/options/space-before-selector-delimiter/lint/test.js
new file mode 100644
index 00000000..e69de29b
diff --git a/test/options/space-before-selector-delimiter/process/css/test-2.expected.css b/test/options/space-before-selector-delimiter/process/css/test-2.expected.css
new file mode 100644
index 00000000..90b232e1
--- /dev/null
+++ b/test/options/space-before-selector-delimiter/process/css/test-2.expected.css
@@ -0,0 +1,6 @@
+a ,b { color: red }
+a , b { color: red }
+a ,b { color: red }
+a ,
+b { color: red }
+a+b ,c>d ,e{ color: red }
\ No newline at end of file
diff --git a/test/options/space-before-selector-delimiter/process/css/test-3.expected.css b/test/options/space-before-selector-delimiter/process/css/test-3.expected.css
new file mode 100644
index 00000000..1b454c84
--- /dev/null
+++ b/test/options/space-before-selector-delimiter/process/css/test-3.expected.css
@@ -0,0 +1,12 @@
+a
+ ,b { color: red }
+a
+ , b { color: red }
+a
+ ,b { color: red }
+a
+ ,
+b { color: red }
+a+b
+ ,c>d
+ ,e{ color: red }
\ No newline at end of file
diff --git a/test/options/space-before-selector-delimiter/process/css/test.css b/test/options/space-before-selector-delimiter/process/css/test.css
new file mode 100644
index 00000000..cd8e7d77
--- /dev/null
+++ b/test/options/space-before-selector-delimiter/process/css/test.css
@@ -0,0 +1,6 @@
+a,b { color: red }
+a, b { color: red }
+a ,b { color: red }
+a,
+b { color: red }
+a+b,c>d,e{ color: red }
\ No newline at end of file
diff --git a/test/options/space-before-selector-delimiter/process/css/test.expected.css b/test/options/space-before-selector-delimiter/process/css/test.expected.css
new file mode 100644
index 00000000..3c566c55
--- /dev/null
+++ b/test/options/space-before-selector-delimiter/process/css/test.expected.css
@@ -0,0 +1,6 @@
+a,b { color: red }
+a, b { color: red }
+a,b { color: red }
+a,
+b { color: red }
+a+b,c>d,e{ color: red }
\ No newline at end of file
diff --git a/test/options/space-before-selector-delimiter/process/test.js b/test/options/space-before-selector-delimiter/process/test.js
new file mode 100644
index 00000000..828e820f
--- /dev/null
+++ b/test/options/space-before-selector-delimiter/process/test.js
@@ -0,0 +1,35 @@
+let Test = require('../../option_test');
+
+describe('Option `space-before-selector-delimiter`, process', function() {
+ describe('css', function() {
+ it('Array value => should not change anything', function() {
+ let test = new Test(this, {'space-before-selector-delimiter': ['', ' ']});
+ return test.shouldBeEqual('test.css');
+ });
+
+ it('Invalid string value => should not change anything', function() {
+ let test = new Test(this, {'space-before-selector-delimiter': ' nani '});
+ return test.shouldBeEqual('test.css');
+ });
+
+ it('Float number value => should not change anything', function() {
+ let test = new Test(this, {'space-before-selector-delimiter': 3.5});
+ return test.shouldBeEqual('test.css');
+ });
+
+ it('Integer value => should set proper space before selector delimiter', function() {
+ let test = new Test(this, {'space-before-selector-delimiter': 0});
+ return test.shouldBeEqual('test.css', 'test.expected.css');
+ });
+
+ it('Valid string value (spaces only) => should set proper space before selector delimiter', function() {
+ let test = new Test(this, {'space-before-selector-delimiter': ' '});
+ return test.shouldBeEqual('test.css', 'test-2.expected.css');
+ });
+
+ it('Valid string value (spaces and newlines) => should set proper space before selector delimiter', function() {
+ let test = new Test(this, {'space-before-selector-delimiter': '\n '});
+ return test.shouldBeEqual('test.css', 'test-3.expected.css');
+ });
+ });
+});
diff --git a/test/options/space-between-declarations/detect/test.js b/test/options/space-between-declarations/detect/test.js
new file mode 100644
index 00000000..e69de29b
diff --git a/test/options/space-between-declarations/lint/test.js b/test/options/space-between-declarations/lint/test.js
new file mode 100644
index 00000000..e69de29b
diff --git a/test/options/space-between-declarations/process/css/comments.css b/test/options/space-between-declarations/process/css/comments.css
new file mode 100644
index 00000000..0d8b9310
--- /dev/null
+++ b/test/options/space-between-declarations/process/css/comments.css
@@ -0,0 +1,8 @@
+a {
+ color: tomato;
+ /* comment */
+ top: 0; /* comment */
+ left: 10px;
+ right: 0;
+ /* comment */
+ }
diff --git a/test/options/space-between-declarations/process/css/comments.expected.css b/test/options/space-between-declarations/process/css/comments.expected.css
new file mode 100644
index 00000000..5903c202
--- /dev/null
+++ b/test/options/space-between-declarations/process/css/comments.expected.css
@@ -0,0 +1,6 @@
+a {
+ color: tomato;
+ /* comment */
+ top: 0; /* comment */ left: 10px; right: 0;
+ /* comment */
+ }
diff --git a/test/options/space-between-declarations/process/css/integer-value.css b/test/options/space-between-declarations/process/css/integer-value.css
new file mode 100644
index 00000000..de2c6491
--- /dev/null
+++ b/test/options/space-between-declarations/process/css/integer-value.css
@@ -0,0 +1,6 @@
+a {
+ color: tomato;
+
+ top: 0;
+ left: 10px
+ }
diff --git a/test/options/space-between-declarations/process/css/integer-value.expected.css b/test/options/space-between-declarations/process/css/integer-value.expected.css
new file mode 100644
index 00000000..5d120170
--- /dev/null
+++ b/test/options/space-between-declarations/process/css/integer-value.expected.css
@@ -0,0 +1,3 @@
+a {
+ color: tomato;top: 0;left: 10px
+ }
diff --git a/test/options/space-between-declarations/process/css/issue-239.css b/test/options/space-between-declarations/process/css/issue-239.css
new file mode 100644
index 00000000..ccf6ba7c
--- /dev/null
+++ b/test/options/space-between-declarations/process/css/issue-239.css
@@ -0,0 +1,2 @@
+html{
+ color:#444;font-family:proxima-nova;font-size:62.5%;font-weight:300;line-height:1.5;}
diff --git a/test/options/space-between-declarations/process/css/issue-239.expected.css b/test/options/space-between-declarations/process/css/issue-239.expected.css
new file mode 100644
index 00000000..ab145f70
--- /dev/null
+++ b/test/options/space-between-declarations/process/css/issue-239.expected.css
@@ -0,0 +1,6 @@
+html{
+ color:#444;
+ font-family:proxima-nova;
+ font-size:62.5%;
+ font-weight:300;
+ line-height:1.5;}
diff --git a/test/options/space-between-declarations/process/css/issue-378.css b/test/options/space-between-declarations/process/css/issue-378.css
new file mode 100644
index 00000000..4c8dbfb6
--- /dev/null
+++ b/test/options/space-between-declarations/process/css/issue-378.css
@@ -0,0 +1,3 @@
+@media (min-width: 768px) {
+ body { background: red; }
+}
diff --git a/test/options/space-between-declarations/process/css/space-newline-value.css b/test/options/space-between-declarations/process/css/space-newline-value.css
new file mode 100644
index 00000000..ff86611c
--- /dev/null
+++ b/test/options/space-between-declarations/process/css/space-newline-value.css
@@ -0,0 +1,4 @@
+a {
+ color: tomato;top: 0; left: 10px;
+ right: 0; width: 100% \9;
+}
diff --git a/test/options/space-between-declarations/process/css/space-newline-value.expected.css b/test/options/space-between-declarations/process/css/space-newline-value.expected.css
new file mode 100644
index 00000000..17c237ac
--- /dev/null
+++ b/test/options/space-between-declarations/process/css/space-newline-value.expected.css
@@ -0,0 +1,7 @@
+a {
+ color: tomato;
+ top: 0;
+ left: 10px;
+ right: 0;
+ width: 100% \9;
+}
diff --git a/test/options/space-between-declarations/process/css/space-value.css b/test/options/space-between-declarations/process/css/space-value.css
new file mode 100644
index 00000000..de2c6491
--- /dev/null
+++ b/test/options/space-between-declarations/process/css/space-value.css
@@ -0,0 +1,6 @@
+a {
+ color: tomato;
+
+ top: 0;
+ left: 10px
+ }
diff --git a/test/options/space-between-declarations/process/css/space-value.expected.css b/test/options/space-between-declarations/process/css/space-value.expected.css
new file mode 100644
index 00000000..6795d9c4
--- /dev/null
+++ b/test/options/space-between-declarations/process/css/space-value.expected.css
@@ -0,0 +1,3 @@
+a {
+ color: tomato; top: 0; left: 10px
+ }
diff --git a/test/options/space-between-declarations/process/css/test.css b/test/options/space-between-declarations/process/css/test.css
new file mode 100644
index 00000000..7a75e51d
--- /dev/null
+++ b/test/options/space-between-declarations/process/css/test.css
@@ -0,0 +1,2 @@
+a {
+ animal: panda;color: tomato; top: 0; }
diff --git a/test/options/space-between-declarations/process/test.js b/test/options/space-between-declarations/process/test.js
new file mode 100644
index 00000000..f230d06c
--- /dev/null
+++ b/test/options/space-between-declarations/process/test.js
@@ -0,0 +1,50 @@
+let Test = require('../../option_test');
+
+describe('Option `space-between-declarations`, process', function() {
+ describe('css', function() {
+ it('Array value => should not change anything', function() {
+ let test = new Test(this, {'space-between-declarations': ['', ' ']});
+ return test.shouldBeEqual('test.css');
+ });
+
+ it('Invalid string value => should not change anything', function() {
+ let test = new Test(this, {'space-between-declarations': ' nani '});
+ return test.shouldBeEqual('test.css');
+ });
+
+ it('Float number value => should not change anything', function() {
+ let test = new Test(this, {'space-between-declarations': 3.5});
+ return test.shouldBeEqual('test.css');
+ });
+
+ it('Integer value => should set proper space after declaration', function() {
+ let test = new Test(this, {'space-between-declarations': 0});
+ return test.shouldBeEqual('integer-value.css', 'integer-value.expected.css');
+ });
+
+ it('Valid string value (spaces only) => should set proper space after declaration', function() {
+ let test = new Test(this, {'space-between-declarations': ' '});
+ return test.shouldBeEqual('space-value.css', 'space-value.expected.css');
+ });
+
+ it('Valid string value (spaces and newlines) => should set proper space after declaration', function() {
+ let test = new Test(this, {'space-between-declarations': '\n '});
+ return test.shouldBeEqual('space-newline-value.css', 'space-newline-value.expected.css');
+ });
+
+ it('Should leave comments as is', function() {
+ let test = new Test(this, {'space-between-declarations': 1});
+ return test.shouldBeEqual('comments.css', 'comments.expected.css');
+ });
+
+ it('Issue 239', function() {
+ let test = new Test(this, {'space-between-declarations': '\n '});
+ return test.shouldBeEqual('issue-239.css', 'issue-239.expected.css');
+ });
+
+ it('Issue 378', function() {
+ let test = new Test(this, {'space-between-declarations': '\n'});
+ return test.shouldBeEqual('issue-378.css');
+ });
+ });
+});
diff --git a/test/options/strip-spaces/detect/test.js b/test/options/strip-spaces/detect/test.js
new file mode 100644
index 00000000..e121275c
--- /dev/null
+++ b/test/options/strip-spaces/detect/test.js
@@ -0,0 +1,77 @@
+let Test = require('../../option_test');
+
+describe('Option `strip-spaces`, detect', function() {
+ describe('css', function() {
+ it('Should detect strip-spaces option set to `true`', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['strip-spaces'],
+ 'a { color: red }',
+ {'strip-spaces': true}
+ );
+ });
+
+ it('Should detect strip-spaces option set to `false`', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['strip-spaces'],
+ 'a { color: red } ',
+ {'strip-spaces': false}
+ );
+ });
+
+ it('Should detect strip-spaces option set to `true` with newline', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['strip-spaces'],
+ 'a { color: red }\nb { color: blue }',
+ {'strip-spaces': true}
+ );
+ });
+
+ it('Should detect strip-spaces option set to `false` with newline', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['strip-spaces'],
+ 'a { color: red } \nb { color: blue }',
+ {'strip-spaces': false}
+ );
+ });
+
+ it('Should detect strip-spaces option set to `true` inside a value', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['strip-spaces'],
+ 'a {\n color:\n red }',
+ {'strip-spaces': true}
+ );
+ });
+
+ it('Should detect strip-spaces option set to `false` inside a value', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['strip-spaces'],
+ 'a {\n color: \n red }',
+ {'strip-spaces': false}
+ );
+ });
+
+ it('Should detect strip-spaces option set to `true` if the only trailing space is the last newline', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['strip-spaces'],
+ 'a { color: red }\n',
+ {'strip-spaces': true}
+ );
+ });
+
+ it('Should detect strip-spaces option set to `false` if there is more than one newline at the end', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['strip-spaces'],
+ 'a { color: red }\n\n',
+ {'strip-spaces': false}
+ );
+ });
+ });
+});
diff --git a/test/options/strip-spaces/lint/test.js b/test/options/strip-spaces/lint/test.js
new file mode 100644
index 00000000..e69de29b
diff --git a/test/options/strip-spaces/process/test.js b/test/options/strip-spaces/process/test.js
new file mode 100644
index 00000000..1c73598c
--- /dev/null
+++ b/test/options/strip-spaces/process/test.js
@@ -0,0 +1,37 @@
+var assert = require('assert');
+let Test = require('../../option_test');
+
+describe('Option `strip-spaces`, process', function() {
+ describe('css', function() {
+ it('Invalid value should not trim trailing spaces', function() {
+ let test = new Test(this, {'strip-spaces': 'foobar'});
+ return test.comb.processString('a { color: red } \n')
+ .then(function(actual) {
+ assert.equal(actual, 'a { color: red } \n');
+ });
+ });
+
+ it('Boolean true value should trim all trailing spaces', function() {
+ let test = new Test(this, {'strip-spaces': true});
+ return test.comb.processString(
+ 'a { color: red } \n' +
+ 'a{color:red}\t /* foobar */\t \n' +
+ 'a {color:red} \n \n'
+ ).then(function(actual) {
+ assert.equal(actual,
+ 'a { color: red }\n' +
+ 'a{color:red}\t /* foobar */\n' +
+ 'a {color:red}\n');
+ });
+ });
+
+ it('Boolean true value should trim trailing spaces at eof', function() {
+ let test = new Test(this, {'strip-spaces': true});
+ return test.comb.processString(
+ 'a {color:red} '
+ ).then(function(actual) {
+ assert.equal(actual, 'a {color:red}');
+ });
+ });
+ });
+});
diff --git a/test/options/tab-size/detect/test.js b/test/options/tab-size/detect/test.js
new file mode 100644
index 00000000..e69de29b
diff --git a/test/options/tab-size/lint/test.js b/test/options/tab-size/lint/test.js
new file mode 100644
index 00000000..e69de29b
diff --git a/test/options/tab-size/process/css/test.css b/test/options/tab-size/process/css/test.css
new file mode 100644
index 00000000..a5ff8ef6
--- /dev/null
+++ b/test/options/tab-size/process/css/test.css
@@ -0,0 +1,4 @@
+a {
+ color: tomato;
+ top: 0;
+ }
diff --git a/test/options/tab-size/process/css/test.expected.css b/test/options/tab-size/process/css/test.expected.css
new file mode 100644
index 00000000..248453a5
--- /dev/null
+++ b/test/options/tab-size/process/css/test.expected.css
@@ -0,0 +1,4 @@
+a {
+ color: tomato;
+ top: 0;
+ }
diff --git a/test/options/tab-size/process/test.js b/test/options/tab-size/process/test.js
new file mode 100644
index 00000000..ea4a4650
--- /dev/null
+++ b/test/options/tab-size/process/test.js
@@ -0,0 +1,20 @@
+let Test = require('../../option_test');
+
+describe('Option `tab-size`, process', function() {
+ describe('css', function() {
+ it('Test 1: String value => should not change anything', function() {
+ let test = new Test(this, {'tab-size': ' '});
+ return test.shouldBeEqual('test.css');
+ });
+
+ it('Test 2: Float value => should not change anything', function() {
+ let test = new Test(this, {'tab-size': 4.5});
+ return test.shouldBeEqual('test.css');
+ });
+
+ it('Test 3: Integer value => should replace tabs with proper number of spaces', function() {
+ let test = new Test(this, {'tab-size': 4});
+ return test.shouldBeEqual('test.css', 'test.expected.css');
+ });
+ });
+});
diff --git a/test/options/unitless-zero/detect/test.js b/test/options/unitless-zero/detect/test.js
new file mode 100644
index 00000000..5d407a49
--- /dev/null
+++ b/test/options/unitless-zero/detect/test.js
@@ -0,0 +1,69 @@
+let Test = require('../../option_test');
+
+describe('Option `unitless-zero`, detect', function() {
+ describe('css', function() {
+ it('Should detect unitless zero option', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['unitless-zero'],
+ 'a { width: 0 }',
+ {'unitless-zero': true}
+ );
+ });
+
+ it('Should detect zero with unit', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['unitless-zero'],
+ 'a { width: 0px }',
+ {'unitless-zero': false}
+ );
+ });
+
+ it('Should detect unitless zero option with multiple values', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['unitless-zero'],
+ 'a { padding: 0px 0 0 }',
+ {'unitless-zero': true}
+ );
+ });
+
+ it('Should detect zero with unit and multiple values', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['unitless-zero'],
+ 'a { padding: 0px 0 0em }',
+ {'unitless-zero': false}
+ );
+ });
+
+ it('Shouldn’t detect unitless zero option if there is no unit', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['unitless-zero'],
+ 'a { color: red }',
+ {}
+ );
+ });
+
+ it('Shouldn’t detect unitless zero option if there is `deg` unit', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['unitless-zero'],
+ 'a { transform: rotate(0deg) }',
+ {}
+ );
+ });
+
+ it('Should detect unitless zero option with percents', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['unitless-zero'],
+ 'a { padding: 0% 0 0 }',
+ {'unitless-zero': true}
+ );
+ });
+ });
+});
+
diff --git a/test/options/unitless-zero/lint/test.js b/test/options/unitless-zero/lint/test.js
new file mode 100644
index 00000000..e69de29b
diff --git a/test/options/unitless-zero/process/css/issue-394.css b/test/options/unitless-zero/process/css/issue-394.css
new file mode 100644
index 00000000..065b1142
--- /dev/null
+++ b/test/options/unitless-zero/process/css/issue-394.css
@@ -0,0 +1,6 @@
+.example {
+ bottom: 0em;
+ left: 0.5em;
+ right: 1em;
+ top: 1.5em;
+}
diff --git a/test/options/unitless-zero/process/css/issue-394.expected.css b/test/options/unitless-zero/process/css/issue-394.expected.css
new file mode 100644
index 00000000..dc9de0f0
--- /dev/null
+++ b/test/options/unitless-zero/process/css/issue-394.expected.css
@@ -0,0 +1,6 @@
+.example {
+ bottom: 0;
+ left: 0.5em;
+ right: 1em;
+ top: 1.5em;
+}
diff --git a/test/options/unitless-zero/process/less/issue-389.less b/test/options/unitless-zero/process/less/issue-389.less
new file mode 100644
index 00000000..f0626d68
--- /dev/null
+++ b/test/options/unitless-zero/process/less/issue-389.less
@@ -0,0 +1,4 @@
+.test
+{
+ width: 100%;
+}
diff --git a/test/options/unitless-zero/process/test.js b/test/options/unitless-zero/process/test.js
new file mode 100644
index 00000000..1f2057aa
--- /dev/null
+++ b/test/options/unitless-zero/process/test.js
@@ -0,0 +1,54 @@
+var assert = require('assert');
+let Test = require('../../option_test');
+
+describe('Option `unitless-zero`, process', function() {
+ describe('css', function() {
+ it('Should remove units in zero-valued dimensions', function() {
+ let test = new Test(this, {'unitless-zero': true});
+ return test.comb.processString(
+ 'div { margin: 0em; padding: 0px }'
+ ).then(function(actual) {
+ assert.equal(actual, 'div { margin: 0; padding: 0 }');
+ });
+ });
+
+ it('Should remove units in zero-valued dimensions, test 2', function() {
+ let test = new Test(this, {'unitless-zero': true});
+ return test.comb.processString(
+ 'div { margin: 0% }'
+ ).then(function(actual) {
+ assert.equal(actual, 'div { margin: 0 }');
+ });
+ });
+
+ it('Should remove units in zero-valued media-query params', function() {
+ let test = new Test(this, {'unitless-zero': true});
+ return test.comb.processString(
+ '@media all and (min-width: 0px) { div { margin: 0em; padding: 0px } }'
+ ).then(function(actual) {
+ assert.equal(actual, '@media all and (min-width: 0) { div { margin: 0; padding: 0 } }');
+ });
+ });
+
+ it('Should not remove units (degs) in rotate property', function() {
+ let test = new Test(this, {'unitless-zero': true});
+ return test.comb.processString(
+ 'div { -webkit-transform: rotate(0deg); }'
+ ).then(function(actual) {
+ assert.equal(actual, 'div { -webkit-transform: rotate(0deg); }');
+ });
+ });
+
+ it('Issue 394', function() {
+ let test = new Test(this, {'unitless-zero': true});
+ return test.shouldBeEqual('issue-394.css', 'issue-394.expected.css');
+ });
+ });
+
+ describe('less', function() {
+ it('Issue 389', function() {
+ let test = new Test(this, {'unitless-zero': true});
+ return test.shouldBeEqual('issue-389.less');
+ });
+ });
+});
diff --git a/test/vendor-prefix-align/already-aligned.css b/test/options/vendor-prefix-align/detect/css/already-aligned.css
similarity index 100%
rename from test/vendor-prefix-align/already-aligned.css
rename to test/options/vendor-prefix-align/detect/css/already-aligned.css
diff --git a/test/vendor-prefix-align/complex.css b/test/options/vendor-prefix-align/detect/css/complex.css
similarity index 100%
rename from test/vendor-prefix-align/complex.css
rename to test/options/vendor-prefix-align/detect/css/complex.css
diff --git a/test/vendor-prefix-align/complex.expected.css b/test/options/vendor-prefix-align/detect/css/complex.expected.css
similarity index 100%
rename from test/vendor-prefix-align/complex.expected.css
rename to test/options/vendor-prefix-align/detect/css/complex.expected.css
diff --git a/test/vendor-prefix-align/property-align.css b/test/options/vendor-prefix-align/detect/css/property-align.css
similarity index 100%
rename from test/vendor-prefix-align/property-align.css
rename to test/options/vendor-prefix-align/detect/css/property-align.css
diff --git a/test/vendor-prefix-align/property-align.expected.css b/test/options/vendor-prefix-align/detect/css/property-align.expected.css
similarity index 100%
rename from test/vendor-prefix-align/property-align.expected.css
rename to test/options/vendor-prefix-align/detect/css/property-align.expected.css
diff --git a/test/vendor-prefix-align/value-align.css b/test/options/vendor-prefix-align/detect/css/value-align.css
similarity index 100%
rename from test/vendor-prefix-align/value-align.css
rename to test/options/vendor-prefix-align/detect/css/value-align.css
diff --git a/test/vendor-prefix-align/value-align.expected.css b/test/options/vendor-prefix-align/detect/css/value-align.expected.css
similarity index 100%
rename from test/vendor-prefix-align/value-align.expected.css
rename to test/options/vendor-prefix-align/detect/css/value-align.expected.css
diff --git a/test/options/vendor-prefix-align/detect/test.js b/test/options/vendor-prefix-align/detect/test.js
new file mode 100644
index 00000000..ca683896
--- /dev/null
+++ b/test/options/vendor-prefix-align/detect/test.js
@@ -0,0 +1,86 @@
+let Test = require('../../option_test');
+
+describe('Option `vendor-prefix-align`, detect', function() {
+ describe('css', function() {
+ it('Shouldn not detect anything if there are no prefixed groups', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['vendor-prefix-align'],
+ 'a{ color: red }a{ -webkit-transform: translateZ(0) }',
+ {}
+ );
+ });
+
+ it('Shouldn detect vendor-prefix-align as false in properties', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['vendor-prefix-align'],
+ test.readFile('property-align.css'),
+ {'vendor-prefix-align': false}
+ );
+ });
+
+ it('Shouldn detect vendor-prefix-align as true in properties', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['vendor-prefix-align'],
+ test.readFile('property-align.expected.css'),
+ {'vendor-prefix-align': true}
+ );
+ });
+
+ it('Shouldn detect vendor-prefix-align as false in values', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['vendor-prefix-align'],
+ test.readFile('value-align.css'),
+ {'vendor-prefix-align': false}
+ );
+ });
+
+ it('Shouldn detect vendor-prefix-align as true in values', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['vendor-prefix-align'],
+ test.readFile('value-align.expected.css'),
+ {'vendor-prefix-align': true}
+ );
+ });
+
+ it('Shouldn detect vendor-prefix-align as true, test 1', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['vendor-prefix-align'],
+ test.readFile('already-aligned.css'),
+ {'vendor-prefix-align': true}
+ );
+ });
+
+ it('Shouldn detect vendor-prefix-align as true, test 2', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['vendor-prefix-align'],
+ test.readFile('complex.expected.css'),
+ {'vendor-prefix-align': true}
+ );
+ });
+
+ it('Shouldn detect vendor-prefix-align as false', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['vendor-prefix-align'],
+ test.readFile('complex.css'),
+ {'vendor-prefix-align': false}
+ );
+ });
+
+ it('Should not detect anything in simple case', function() {
+ let test = new Test(this);
+ test.shouldDetect(
+ ['vendor-prefix-align'],
+ 'a{border:0;}',
+ {}
+ );
+ });
+ });
+});
diff --git a/test/options/vendor-prefix-align/lint/test.js b/test/options/vendor-prefix-align/lint/test.js
new file mode 100644
index 00000000..e69de29b
diff --git a/test/vendor-prefix-align/already-aligned.expected.css b/test/options/vendor-prefix-align/process/css/already-aligned.css
similarity index 100%
rename from test/vendor-prefix-align/already-aligned.expected.css
rename to test/options/vendor-prefix-align/process/css/already-aligned.css
diff --git a/test/options/vendor-prefix-align/process/css/already-aligned.expected.css b/test/options/vendor-prefix-align/process/css/already-aligned.expected.css
new file mode 100644
index 00000000..35b7eeec
--- /dev/null
+++ b/test/options/vendor-prefix-align/process/css/already-aligned.expected.css
@@ -0,0 +1,11 @@
+.radio-button_theme_normal .radio-button__radio:before
+{
+ background: rgba(0,0,0,.4);
+ background: -webkit-linear-gradient(top, rgba(0,0,0,.2) 0,rgba(0,0,0,.4) 100%);
+ background: -moz-linear-gradient(top, rgba(0,0,0,.2) 0, rgba(0,0,0,.4) 100%);
+ background: -o-linear-gradient(top, rgba(0,0,0,.2) 0,rgba(0,0,0,.4) 100%);
+ background: linear-gradient(to bottom, rgba(0,0,0,.2) 0,rgba(0,0,0,.4) 100%);
+
+ -moz-box-shadow: 0 1px 0 rgba(0,0,0,.07);
+ box-shadow: 0 1px 0 rgba(0,0,0,.07);
+}
diff --git a/test/options/vendor-prefix-align/process/css/both.css b/test/options/vendor-prefix-align/process/css/both.css
new file mode 100644
index 00000000..3791d296
--- /dev/null
+++ b/test/options/vendor-prefix-align/process/css/both.css
@@ -0,0 +1,5 @@
+.serp-block__head_animation_yes
+{
+ -webkit-transition: -webkit-transform 150ms linear;
+ transition: transform 150ms linear;
+}
diff --git a/test/options/vendor-prefix-align/process/css/both.expected.css b/test/options/vendor-prefix-align/process/css/both.expected.css
new file mode 100644
index 00000000..f58aa02a
--- /dev/null
+++ b/test/options/vendor-prefix-align/process/css/both.expected.css
@@ -0,0 +1,5 @@
+.serp-block__head_animation_yes
+{
+ -webkit-transition: -webkit-transform 150ms linear;
+ transition: transform 150ms linear;
+}
diff --git a/test/options/vendor-prefix-align/process/css/complex.css b/test/options/vendor-prefix-align/process/css/complex.css
new file mode 100644
index 00000000..3f896df2
--- /dev/null
+++ b/test/options/vendor-prefix-align/process/css/complex.css
@@ -0,0 +1,30 @@
+@media all and (min-width:0)
+{
+ .radio-button_theme_normal .radio-button__radio:before
+ {
+ background: rgba(0,0,0,.4);
+ background: -webkit-linear-gradient(top, rgba(0,0,0,.2) 0,rgba(0,0,0,.4) 100%);
+ background: -moz-linear-gradient(top, rgba(0,0,0,.2) 0, rgba(0,0,0,.4) 100%);
+ background: -o-linear-gradient(top, rgba(0,0,0,.2) 0,rgba(0,0,0,.4) 100%);
+ background: linear-gradient(to bottom, rgba(0,0,0,.2) 0,rgba(0,0,0,.4) 100%);
+ -moz-box-shadow: 0 1px 0 rgba(0,0,0,.07);
+ box-shadow: 0 1px 0 rgba(0,0,0,.07);
+ }
+
+ /* :after — фон */
+ .radio-button_theme_normal .radio-button__radio:after
+ {
+ background: #fff;
+ background: -webkit-linear-gradient(top, #fff 0,#eee 100%);
+ background: -moz-linear-gradient(top, #fff 0, #eee 100%);
+ background: -o-linear-gradient(top, #fff 0,#eee 100%);
+ background: linear-gradient(to bottom, #fff 0,#eee 100%);
+ }
+
+ /* _focused_yes */
+ .radio-button_theme_normal .radio-button__radio_focused_yes:before
+ {
+ -moz-box-shadow: 0 0 6px 2px rgba(255,204,0,.7), 0 1px 0 rgba(0,0,0,.07);
+ box-shadow: 0 0 6px 2px rgba(255,204,0,.7), 0 1px 0 rgba(0,0,0,.07);
+ }
+}
diff --git a/test/options/vendor-prefix-align/process/css/complex.expected.css b/test/options/vendor-prefix-align/process/css/complex.expected.css
new file mode 100644
index 00000000..688ecd61
--- /dev/null
+++ b/test/options/vendor-prefix-align/process/css/complex.expected.css
@@ -0,0 +1,30 @@
+@media all and (min-width:0)
+{
+ .radio-button_theme_normal .radio-button__radio:before
+ {
+ background: rgba(0,0,0,.4);
+ background: -webkit-linear-gradient(top, rgba(0,0,0,.2) 0,rgba(0,0,0,.4) 100%);
+ background: -moz-linear-gradient(top, rgba(0,0,0,.2) 0, rgba(0,0,0,.4) 100%);
+ background: -o-linear-gradient(top, rgba(0,0,0,.2) 0,rgba(0,0,0,.4) 100%);
+ background: linear-gradient(to bottom, rgba(0,0,0,.2) 0,rgba(0,0,0,.4) 100%);
+ -moz-box-shadow: 0 1px 0 rgba(0,0,0,.07);
+ box-shadow: 0 1px 0 rgba(0,0,0,.07);
+ }
+
+ /* :after — фон */
+ .radio-button_theme_normal .radio-button__radio:after
+ {
+ background: #fff;
+ background: -webkit-linear-gradient(top, #fff 0,#eee 100%);
+ background: -moz-linear-gradient(top, #fff 0, #eee 100%);
+ background: -o-linear-gradient(top, #fff 0,#eee 100%);
+ background: linear-gradient(to bottom, #fff 0,#eee 100%);
+ }
+
+ /* _focused_yes */
+ .radio-button_theme_normal .radio-button__radio_focused_yes:before
+ {
+ -moz-box-shadow: 0 0 6px 2px rgba(255,204,0,.7), 0 1px 0 rgba(0,0,0,.07);
+ box-shadow: 0 0 6px 2px rgba(255,204,0,.7), 0 1px 0 rgba(0,0,0,.07);
+ }
+}
diff --git a/test/options/vendor-prefix-align/process/css/issue-193.css b/test/options/vendor-prefix-align/process/css/issue-193.css
new file mode 100644
index 00000000..e0047dd4
--- /dev/null
+++ b/test/options/vendor-prefix-align/process/css/issue-193.css
@@ -0,0 +1,2 @@
+li {color: #7799c8;
+ font-size: 10px;}
diff --git a/test/options/vendor-prefix-align/process/css/issue-193.expected.css b/test/options/vendor-prefix-align/process/css/issue-193.expected.css
new file mode 100644
index 00000000..e0047dd4
--- /dev/null
+++ b/test/options/vendor-prefix-align/process/css/issue-193.expected.css
@@ -0,0 +1,2 @@
+li {color: #7799c8;
+ font-size: 10px;}
diff --git a/test/options/vendor-prefix-align/process/css/issue-241.css b/test/options/vendor-prefix-align/process/css/issue-241.css
new file mode 100644
index 00000000..e562a614
--- /dev/null
+++ b/test/options/vendor-prefix-align/process/css/issue-241.css
@@ -0,0 +1,6 @@
+*
+{
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
diff --git a/test/options/vendor-prefix-align/process/css/issue-241.expected.css b/test/options/vendor-prefix-align/process/css/issue-241.expected.css
new file mode 100644
index 00000000..bbc0e348
--- /dev/null
+++ b/test/options/vendor-prefix-align/process/css/issue-241.expected.css
@@ -0,0 +1,6 @@
+*
+{
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ }
diff --git a/test/options/vendor-prefix-align/process/css/multiline-comments.css b/test/options/vendor-prefix-align/process/css/multiline-comments.css
new file mode 100644
index 00000000..33c66c5e
--- /dev/null
+++ b/test/options/vendor-prefix-align/process/css/multiline-comments.css
@@ -0,0 +1,9 @@
+a
+{
+ -webkit-color: panda;
+ /*
+ * /\_/\
+ * ( o.o )
+ * > ^ <
+ */ -moz-color: panda;
+}
diff --git a/test/options/vendor-prefix-align/process/css/multiline-comments.expected.css b/test/options/vendor-prefix-align/process/css/multiline-comments.expected.css
new file mode 100644
index 00000000..6bc01b39
--- /dev/null
+++ b/test/options/vendor-prefix-align/process/css/multiline-comments.expected.css
@@ -0,0 +1,9 @@
+a
+{
+ -webkit-color: panda;
+ /*
+ * /\_/\
+ * ( o.o )
+ * > ^ <
+ */ -moz-color: panda;
+}
diff --git a/test/options/vendor-prefix-align/process/css/one-line-2.css b/test/options/vendor-prefix-align/process/css/one-line-2.css
new file mode 100644
index 00000000..6050079f
--- /dev/null
+++ b/test/options/vendor-prefix-align/process/css/one-line-2.css
@@ -0,0 +1 @@
+a{top:0}
diff --git a/test/options/vendor-prefix-align/process/css/one-line-2.expected.css b/test/options/vendor-prefix-align/process/css/one-line-2.expected.css
new file mode 100644
index 00000000..6050079f
--- /dev/null
+++ b/test/options/vendor-prefix-align/process/css/one-line-2.expected.css
@@ -0,0 +1 @@
+a{top:0}
diff --git a/test/options/vendor-prefix-align/process/css/one-line.css b/test/options/vendor-prefix-align/process/css/one-line.css
new file mode 100644
index 00000000..34265960
--- /dev/null
+++ b/test/options/vendor-prefix-align/process/css/one-line.css
@@ -0,0 +1 @@
+a{ -webkit-color:tomato; -moz-color:tomato; top:0 }
diff --git a/test/options/vendor-prefix-align/process/css/one-line.expected.css b/test/options/vendor-prefix-align/process/css/one-line.expected.css
new file mode 100644
index 00000000..34265960
--- /dev/null
+++ b/test/options/vendor-prefix-align/process/css/one-line.expected.css
@@ -0,0 +1 @@
+a{ -webkit-color:tomato; -moz-color:tomato; top:0 }
diff --git a/test/options/vendor-prefix-align/process/css/property-align.css b/test/options/vendor-prefix-align/process/css/property-align.css
new file mode 100644
index 00000000..b19d6b7c
--- /dev/null
+++ b/test/options/vendor-prefix-align/process/css/property-align.css
@@ -0,0 +1,8 @@
+a
+{
+ color: #fff;
+
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
+}
diff --git a/test/options/vendor-prefix-align/process/css/property-align.expected.css b/test/options/vendor-prefix-align/process/css/property-align.expected.css
new file mode 100644
index 00000000..25808572
--- /dev/null
+++ b/test/options/vendor-prefix-align/process/css/property-align.expected.css
@@ -0,0 +1,8 @@
+a
+{
+ color: #fff;
+
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
+}
diff --git a/test/options/vendor-prefix-align/process/css/same-name.css b/test/options/vendor-prefix-align/process/css/same-name.css
new file mode 100644
index 00000000..dbdc5c40
--- /dev/null
+++ b/test/options/vendor-prefix-align/process/css/same-name.css
@@ -0,0 +1,7 @@
+.test
+{
+ -webkit-transition: -webkit-transform .6s;
+ transition: transform .6s;
+ -webkit-transform: scale(.6, .6);
+ transform: scale(.6, .6);
+}
diff --git a/test/options/vendor-prefix-align/process/css/same-name.expected.css b/test/options/vendor-prefix-align/process/css/same-name.expected.css
new file mode 100644
index 00000000..90598073
--- /dev/null
+++ b/test/options/vendor-prefix-align/process/css/same-name.expected.css
@@ -0,0 +1,7 @@
+.test
+{
+ -webkit-transition: -webkit-transform .6s;
+ transition: transform .6s;
+ -webkit-transform: scale(.6, .6);
+ transform: scale(.6, .6);
+}
diff --git a/test/options/vendor-prefix-align/process/css/value-align.css b/test/options/vendor-prefix-align/process/css/value-align.css
new file mode 100644
index 00000000..f15a3620
--- /dev/null
+++ b/test/options/vendor-prefix-align/process/css/value-align.css
@@ -0,0 +1,10 @@
+b
+{
+ color: #fff;
+
+ background: -webkit-linear-gradient(linear, left top, right top, color-stop(0,rgba(255,255,255,0)), color-stop(20%, #fff));
+ background: -moz-linear-gradient(left, rgba(255,255,255,0) 0, #fff 20%);
+ background: -o-linear-gradient(left, rgba(255,255,255,0) 0, #fff 20%);
+ background: -ms-linear-gradient(left, rgba(255,255,255,0) 0, #fff 20%);
+ background: linear-gradient(to right, rgba(255,255,255,0) 0, #fff 20%);
+}
diff --git a/test/options/vendor-prefix-align/process/css/value-align.expected.css b/test/options/vendor-prefix-align/process/css/value-align.expected.css
new file mode 100644
index 00000000..60aba2ee
--- /dev/null
+++ b/test/options/vendor-prefix-align/process/css/value-align.expected.css
@@ -0,0 +1,10 @@
+b
+{
+ color: #fff;
+
+ background: -webkit-linear-gradient(linear, left top, right top, color-stop(0,rgba(255,255,255,0)), color-stop(20%, #fff));
+ background: -moz-linear-gradient(left, rgba(255,255,255,0) 0, #fff 20%);
+ background: -o-linear-gradient(left, rgba(255,255,255,0) 0, #fff 20%);
+ background: -ms-linear-gradient(left, rgba(255,255,255,0) 0, #fff 20%);
+ background: linear-gradient(to right, rgba(255,255,255,0) 0, #fff 20%);
+}
diff --git a/test/options/vendor-prefix-align/process/css/with-comment-property-2.css b/test/options/vendor-prefix-align/process/css/with-comment-property-2.css
new file mode 100644
index 00000000..113d3342
--- /dev/null
+++ b/test/options/vendor-prefix-align/process/css/with-comment-property-2.css
@@ -0,0 +1,3 @@
+a
+{
+ top: 0;/* ololo */margin: 0;}
diff --git a/test/options/vendor-prefix-align/process/css/with-comment-property-2.expected.css b/test/options/vendor-prefix-align/process/css/with-comment-property-2.expected.css
new file mode 100644
index 00000000..113d3342
--- /dev/null
+++ b/test/options/vendor-prefix-align/process/css/with-comment-property-2.expected.css
@@ -0,0 +1,3 @@
+a
+{
+ top: 0;/* ololo */margin: 0;}
diff --git a/test/options/vendor-prefix-align/process/css/with-comment-property.css b/test/options/vendor-prefix-align/process/css/with-comment-property.css
new file mode 100644
index 00000000..9e3bea36
--- /dev/null
+++ b/test/options/vendor-prefix-align/process/css/with-comment-property.css
@@ -0,0 +1,5 @@
+body
+{
+ -webkit-transition: transform 150ms linear;
+ /* comment */ transition: transform 150ms linear;
+}
diff --git a/test/options/vendor-prefix-align/process/css/with-comment-property.expected.css b/test/options/vendor-prefix-align/process/css/with-comment-property.expected.css
new file mode 100644
index 00000000..b65b2105
--- /dev/null
+++ b/test/options/vendor-prefix-align/process/css/with-comment-property.expected.css
@@ -0,0 +1,5 @@
+body
+{
+ -webkit-transition: transform 150ms linear;
+ /* comment */ transition: transform 150ms linear;
+}
diff --git a/test/options/vendor-prefix-align/process/css/with-comment.css b/test/options/vendor-prefix-align/process/css/with-comment.css
new file mode 100644
index 00000000..f792a8cc
--- /dev/null
+++ b/test/options/vendor-prefix-align/process/css/with-comment.css
@@ -0,0 +1,5 @@
+body
+{
+ background: -webkit-linear-gradient(nani);
+ background: /* comment */ -moz-linear-gradient(nani);
+}
diff --git a/test/options/vendor-prefix-align/process/css/with-comment.expected.css b/test/options/vendor-prefix-align/process/css/with-comment.expected.css
new file mode 100644
index 00000000..9d5d161c
--- /dev/null
+++ b/test/options/vendor-prefix-align/process/css/with-comment.expected.css
@@ -0,0 +1,5 @@
+body
+{
+ background: -webkit-linear-gradient(nani);
+ background: /* comment */ -moz-linear-gradient(nani);
+}
diff --git a/test/options/vendor-prefix-align/process/css/without-space.css b/test/options/vendor-prefix-align/process/css/without-space.css
new file mode 100644
index 00000000..09bd955f
--- /dev/null
+++ b/test/options/vendor-prefix-align/process/css/without-space.css
@@ -0,0 +1,5 @@
+body
+{
+ background:-webkit-linear-gradient(nani);
+ background:-moz-linear-gradient(nani);
+}
diff --git a/test/options/vendor-prefix-align/process/css/without-space.expected.css b/test/options/vendor-prefix-align/process/css/without-space.expected.css
new file mode 100644
index 00000000..1be53893
--- /dev/null
+++ b/test/options/vendor-prefix-align/process/css/without-space.expected.css
@@ -0,0 +1,5 @@
+body
+{
+ background:-webkit-linear-gradient(nani);
+ background: -moz-linear-gradient(nani);
+}
diff --git a/test/options/vendor-prefix-align/process/sass/property.sass b/test/options/vendor-prefix-align/process/sass/property.sass
new file mode 100644
index 00000000..b488c7a2
--- /dev/null
+++ b/test/options/vendor-prefix-align/process/sass/property.sass
@@ -0,0 +1,4 @@
+a
+ -webkit-color: panda
+ color: panda
+ -moz-color: panda
diff --git a/test/options/vendor-prefix-align/process/sass/value.expected.sass b/test/options/vendor-prefix-align/process/sass/value.expected.sass
new file mode 100644
index 00000000..4df59dad
--- /dev/null
+++ b/test/options/vendor-prefix-align/process/sass/value.expected.sass
@@ -0,0 +1,4 @@
+a
+ color: panda
+ color: -webkit-panda
+ color: -moz-panda
diff --git a/test/options/vendor-prefix-align/process/sass/value.sass b/test/options/vendor-prefix-align/process/sass/value.sass
new file mode 100644
index 00000000..e3cff569
--- /dev/null
+++ b/test/options/vendor-prefix-align/process/sass/value.sass
@@ -0,0 +1,4 @@
+a
+ color: panda
+ color: -webkit-panda
+ color: -moz-panda
diff --git a/test/options/vendor-prefix-align/process/test.js b/test/options/vendor-prefix-align/process/test.js
new file mode 100644
index 00000000..1b4d70ac
--- /dev/null
+++ b/test/options/vendor-prefix-align/process/test.js
@@ -0,0 +1,95 @@
+let Test = require('../../option_test');
+
+describe('Option `vendor-prefix-align`, process', function() {
+ describe('css', function() {
+ it('Should correctly work when there is comment before property name', function() {
+ let test = new Test(this, {'vendor-prefix-align': true});
+ return test.shouldBeEqual('with-comment-property.css', 'with-comment-property.expected.css');
+ });
+
+ it('Should correctly work when there is comment before property name. Test 2', function() {
+ let test = new Test(this, {'vendor-prefix-align': true});
+ return test.shouldBeEqual('with-comment-property-2.css', 'with-comment-property-2.expected.css');
+ });
+
+ it('Should correctly work when there is comment before property name. Test 3', function() {
+ let test = new Test(this, {'vendor-prefix-align': true});
+ return test.shouldBeEqual('multiline-comments.css', 'multiline-comments.expected.css');
+ });
+
+ it('Should correctly align prefixes in properties', function() {
+ let test = new Test(this, {'vendor-prefix-align': true});
+ return test.shouldBeEqual('property-align.css', 'property-align.expected.css');
+ });
+
+ it('Should correctly align prefixes in values', function() {
+ let test = new Test(this, {'vendor-prefix-align': true});
+ return test.shouldBeEqual('value-align.css', 'value-align.expected.css');
+ });
+
+ it('Should not touch already align prefixes', function() {
+ let test = new Test(this, {'vendor-prefix-align': true});
+ return test.shouldBeEqual('already-aligned.css', 'already-aligned.expected.css');
+ });
+
+ it('Should correctly align prefixes in properties and values at the same time', function() {
+ let test = new Test(this, {'vendor-prefix-align': true});
+ return test.shouldBeEqual('both.css', 'both.expected.css');
+ });
+
+ it('Should correctly work when value and property names are the same', function() {
+ let test = new Test(this, {'vendor-prefix-align': true});
+ return test.shouldBeEqual('same-name.css', 'same-name.expected.css');
+ });
+
+ it('Should correctly work when there is no whitespace after colon', function() {
+ let test = new Test(this, {'vendor-prefix-align': true});
+ return test.shouldBeEqual('without-space.css', 'without-space.expected.css');
+ });
+
+ it('Should correctly work when there is comment after colon', function() {
+ let test = new Test(this, {'vendor-prefix-align': true});
+ return test.shouldBeEqual('with-comment.css', 'with-comment.expected.css');
+ });
+
+ it('Should not do anything with oneliners', function() {
+ let test = new Test(this, {'vendor-prefix-align': true});
+ return test.shouldBeEqual('one-line.css', 'one-line.expected.css');
+ });
+
+ it('Should not do anything with oneliners. Test 2', function() {
+ let test = new Test(this, {'vendor-prefix-align': true});
+ return test.shouldBeEqual('one-line-2.css', 'one-line-2.expected.css');
+ });
+
+ it('Should always correctly align prefixes', function() {
+ let test = new Test(this, {'vendor-prefix-align': true});
+ return test.shouldBeEqual('complex.css', 'complex.expected.css');
+ });
+
+ it('Issue 193: should handle declarations without preceding spaces', function() {
+ let test = new Test(this, {'vendor-prefix-align': true});
+ return test.shouldBeEqual('issue-193.css', 'issue-193.expected.css');
+ });
+
+ it('Issue 241: should not break tabs', function() {
+ let test = new Test(this, {
+ 'block-indent': '\t',
+ 'vendor-prefix-align': true
+ });
+ return test.shouldBeEqual('issue-241.css', 'issue-241.expected.css');
+ });
+ });
+
+ describe('sass', function() {
+ it('Should align prexied values', function() {
+ let test = new Test(this, {'vendor-prefix-align': true});
+ return test.shouldBeEqual('value.sass', 'value.expected.sass');
+ });
+
+ it('Should not align prefixed property names', function() {
+ let test = new Test(this, {'vendor-prefix-align': true});
+ return test.shouldBeEqual('property.sass');
+ });
+ });
+});
diff --git a/test/remove-empty-rulesets.js b/test/remove-empty-rulesets.js
deleted file mode 100644
index ec9ecead..00000000
--- a/test/remove-empty-rulesets.js
+++ /dev/null
@@ -1,99 +0,0 @@
-var Comb = require('../lib/csscomb');
-var assert = require('assert');
-
-describe('options/remove-empty-rulesets', function() {
- var comb;
-
- beforeEach(function() {
- comb = new Comb();
- });
-
- describe('configured with invalid value', function() {
- beforeEach(function() {
- comb.configure({ 'remove-empty-rulesets': 'foobar' });
- });
-
- it('should not remove empty ruleset', function() {
- assert.equal(comb.processString('a { width: 10px; } b {}'), 'a { width: 10px; } b {}');
- });
- });
-
- describe('configured with Boolean "true" value', function() {
- beforeEach(function() {
- comb.configure({ 'remove-empty-rulesets': true });
- });
-
- it('should remove empty ruleset', function() {
- assert.equal(comb.processString(' b {} '), ' ');
- });
-
- it('should leave ruleset with declarations', function() {
- assert.equal(comb.processString('a { width: 10px; }\nb {} '), 'a { width: 10px; }\n ');
- });
-
- it('should leave ruleset with comments', function() {
- assert.equal(comb.processString('a { /* comment */ }\nb {} '), 'a { /* comment */ }\n ');
- });
- });
-});
-
-describe('options/remove-empty-rulesets AST manipulation', function() {
- var rule;
- var nodeContent;
-
- beforeEach(function() {
- rule = require('../lib/options/remove-empty-rulesets.js');
- });
-
- describe('merge adjacent whitespace', function() {
- it('should do nothing with empty content', function() {
- nodeContent = [];
- rule._mergeAdjacentWhitespace(nodeContent);
- assert.deepEqual(nodeContent, []);
- });
-
- it('should do nothing with only one whitespace', function() {
- nodeContent = [['s', ' ']];
- rule._mergeAdjacentWhitespace(nodeContent);
- assert.deepEqual(nodeContent, [['s', ' ']]);
- });
-
- it('should merge two adjacent whitespaces', function() {
- nodeContent = [['s', ' '], ['s', ' \n']];
- rule._mergeAdjacentWhitespace(nodeContent);
- assert.deepEqual(nodeContent, [['s', ' \n']]);
- });
-
- it('should merge three adjacent whitespaces', function() {
- nodeContent = [['s', ' '], ['s', ' \n'], ['s', ' \n']];
- rule._mergeAdjacentWhitespace(nodeContent);
- assert.deepEqual(nodeContent, [['s', ' \n \n']]);
- });
- });
-
- describe('remove empty rulesets', function() {
- it('should do nothing with empty content', function() {
- nodeContent = [];
- rule._removeEmptyRulesets(nodeContent);
- assert.deepEqual(nodeContent, []);
- });
-
- it('should do nothing with no rulesets', function() {
- nodeContent = [['s', ' ']];
- rule._removeEmptyRulesets(nodeContent);
- assert.deepEqual(nodeContent, [['s', ' ']]);
- });
-
- it('should remove empty ruleset', function() {
- nodeContent = [['ruleset', []]];
- rule._removeEmptyRulesets(nodeContent);
- assert.deepEqual(nodeContent, []);
- });
-
- it('should remove two empty rulesets', function() {
- nodeContent = [['s', ' '], ['ruleset', []], ['s', ' \n'], ['ruleset', []]];
- rule._removeEmptyRulesets(nodeContent);
- assert.deepEqual(nodeContent, [['s', ' '], ['s', ' \n']]);
- });
- });
-});
diff --git a/test/rule-indent.js b/test/rule-indent.js
deleted file mode 100644
index 8c44f94a..00000000
--- a/test/rule-indent.js
+++ /dev/null
@@ -1,52 +0,0 @@
-var Comb = require('../lib/csscomb');
-var assert = require('assert');
-
-describe('options/rule-indent', function() {
- var comb;
- beforeEach(function() {
- comb = new Comb();
- });
- it('Invalid Number value should not change rule indent', function() {
- comb.configure({ 'rule-indent': 3.5 });
- assert.equal(
- comb.processString('a {\n color: red }'),
- 'a {\n color: red }'
- );
- });
- it('Invalid String value should not change rule indent', function() {
- comb.configure({ 'rule-indent': 'foobar' });
- assert.equal(
- comb.processString('a {\n color: red }'),
- 'a {\n color: red }'
- );
- });
- it('True Boolean value should set 4 spaces indent', function() {
- comb.configure({ 'rule-indent': true });
- assert.equal(
- comb.processString('a {\n color: red }'),
- 'a {\n color: red }'
- );
- });
- it('Valid Number value should set equal space indent', function() {
- comb.configure({ 'rule-indent': 3 });
- assert.equal(
- comb.processString('a {\n color: red }'),
- 'a {\n color: red }'
- );
- });
- it('Valid String value should set equal indent', function() {
- comb.configure({ 'rule-indent': '\t' });
- assert.equal(
- comb.processString(
- 'a{color:red;background:#fff}\n' +
- 'a { color: red; background: #fff; }\n' +
- 'a {\ncolor:red;\n\nbackground: #fff}\n' +
- 'a { /* foo */ color:red; /* bar */\n\nbackground: #fff\n}\n'
- ),
- 'a{\n\tcolor:red;\n\tbackground:#fff}\n' +
- 'a {\n\tcolor: red;\n\tbackground: #fff; }\n' +
- 'a {\n\tcolor:red;\n\n\tbackground: #fff}\n' +
- 'a { /* foo */\n\tcolor:red; /* bar */\n\n\tbackground: #fff\n}\n'
- );
- });
-});
diff --git a/test/sort-order.js b/test/sort-order.js
deleted file mode 100644
index d1e1bbd6..00000000
--- a/test/sort-order.js
+++ /dev/null
@@ -1,146 +0,0 @@
-var Comb = require('../lib/csscomb');
-var assert = require('assert');
-
-describe('options/sort-order', function() {
-
- var comb;
-
- beforeEach(function() {
- comb = new Comb();
- });
-
- it('Should be in expected order in case of 1 group', function() {
-
- var config = {
- 'sort-order': [
- ['position', 'z-index']
- ]
- };
-
- var input = 'a\n' +
- '{\n' +
- '\tz-index: 2;\n' +
- '\tposition: absolute;\n' +
- '}';
-
- var expected = 'a\n' +
- '{\n' +
- '\tposition: absolute;\n' +
- '\tz-index: 2;\n' +
- '}';
-
- comb.configure(config);
- assert.equal(comb.processString(input), expected);
-
- });
-
- it('Shuld be in expected order in case of multiple groups', function() {
-
- var config = {
- 'sort-order': [
- ['position', 'z-index'],
- ['width', 'height']
- ]
- };
-
- var input = 'a\n' +
- '{\n' +
- '\tz-index: 2;\n' +
- '\tposition: absolute;\n' +
- '\theight: 2px;\n' +
- '\twidth: 2px;\n' +
- '}';
-
- var expected = 'a\n' +
- '{\n' +
- '\tposition: absolute;\n' +
- '\tz-index: 2;\n' +
- '\n' +
- '\twidth: 2px;\n' +
- '\theight: 2px;\n' +
- '}';
-
- comb.configure(config);
- assert.equal(comb.processString(input), expected);
-
- });
-
- it('Should work correctly with comments in case of 1 group', function() {
-
- var config = {
- 'sort-order': [
- ['border-bottom', 'font-style'],
- ]
- };
-
- var input = 'div p em {\n' +
- '\t/* upline comment */\n' +
- '\tfont-style:italic;\n' +
- '\tborder-bottom:1px solid red /* trololo */ /* trololo */\n' +
- '}';
-
- var expected = 'div p em {\n' +
- '\tborder-bottom:1px solid red /* trololo */ /* trololo */\n' +
- '\t/* upline comment */\n' +
- '\tfont-style:italic;\n' +
- '}';
-
- comb.configure(config);
- assert.equal(comb.processString(input), expected);
-
- });
-
- it('Should work correctly with comments in case of multiple groups', function() {
-
- var config = {
- 'sort-order': [
- ['margin'],
- ['padding']
- ]
- };
-
- var input = 'a, b, i /* foobar */\n' +
- '{\n' +
- ' padding: 0;\n' +
- ' margin: 0;\n' +
- ' }';
-
- var expected = 'a, b, i /* foobar */\n' +
- '{\n' +
- ' margin: 0;\n' +
- '\n' +
- ' padding: 0;\n' +
- ' }';
-
- comb.configure(config);
- assert.equal(comb.processString(input), expected);
-
- });
-
- it('Should replace custom delimiters by ours', function() {
-
- var config = {
- 'sort-order': [
- ['margin'],
- ['padding']
- ]
- };
-
- var input = 'div p em {\n' +
- '\tpadding: 1px;\n' +
- ' \n' +
- '\tmargin: 1px;\n' +
- '}';
-
- var expected = 'div p em {\n' +
- '\tmargin: 1px;\n' +
- '\n' +
- '\tpadding: 1px;\n' +
- '}';
-
- comb.configure(config);
- assert.equal(comb.processString(input), expected);
-
- });
-
-});
diff --git a/test/stick-brace.js b/test/stick-brace.js
deleted file mode 100644
index 29c52f99..00000000
--- a/test/stick-brace.js
+++ /dev/null
@@ -1,47 +0,0 @@
-var Comb = require('../lib/csscomb');
-var assert = require('assert');
-
-describe('options/stick-brace', function() {
- var comb;
- beforeEach(function() {
- comb = new Comb();
- });
- it('Invalid String should not change space before brace', function() {
- comb.configure({ 'stick-brace': 'foobar' });
- assert.equal(
- comb.processString('a { color: red }'),
- 'a { color: red }'
- );
- });
- it('True Boolean value should set 1 space before brace', function() {
- comb.configure({ 'stick-brace': true });
- assert.equal(
- comb.processString('a{color:red }'),
- 'a {color:red }'
- );
- });
- it('Valid Number value should set equal space before brace', function() {
- comb.configure({ 'stick-brace': 0 });
- assert.equal(
- comb.processString('a {color:red }'),
- 'a{color:red }'
- );
- });
- it('Valid String value should set equal space before brace', function() {
- comb.configure({ 'stick-brace': '\n' });
- assert.equal(
- comb.processString(
- 'a{ color: red }' +
- 'a, b /* i */ { color: red; }' +
- 'a \t\t \n{color:red\n \n}' +
- 'a /* foo */ {color:red ;\n}' +
- '@media all { .input__control { color: #000;\n \n }\t}'
- ),
- 'a\n{ color: red }' +
- 'a, b /* i */\n{ color: red; }' +
- 'a\n{color:red\n \n}' +
- 'a /* foo */\n{color:red ;\n}' +
- '@media all\n{ .input__control\n{ color: #000;\n \n }\t}'
- );
- });
-});
diff --git a/test/strip-spaces.js b/test/strip-spaces.js
deleted file mode 100644
index 6090272d..00000000
--- a/test/strip-spaces.js
+++ /dev/null
@@ -1,38 +0,0 @@
-var Comb = require('../lib/csscomb');
-var assert = require('assert');
-
-describe('options/strip-space', function() {
- var comb;
- beforeEach(function() {
- comb = new Comb();
- });
- it('Invalid value should not trim trailing spaces', function() {
- comb.configure({ 'strip-spaces': 'foobar' });
- assert.equal(
- comb.processString('a { color: red } \n'),
- 'a { color: red } \n'
- );
- });
- it('Boolean true value should trim all trailing spaces', function() {
- comb.configure({ 'strip-spaces': true });
- assert.equal(
- comb.processString(
- 'a { color: red } \n' +
- 'a{color:red}\t /* foobar */\t \n' +
- 'a {color:red} \n \n'
- ),
- 'a { color: red }\n' +
- 'a{color:red}\t /* foobar */\n' +
- 'a {color:red}\n'
- );
- });
- it('Boolean true value should trim trailing spaces at eof', function() {
- comb.configure({ 'strip-spaces': true });
- assert.equal(
- comb.processString(
- 'a {color:red} '
- ),
- 'a {color:red}'
- );
- });
-});
diff --git a/test/unitless-zero.js b/test/unitless-zero.js
deleted file mode 100644
index 6e6b2b9e..00000000
--- a/test/unitless-zero.js
+++ /dev/null
@@ -1,45 +0,0 @@
-var Comb = require('../lib/csscomb');
-var assert = require('assert');
-
-describe('options/unitless-zero', function() {
- var comb;
-
- beforeEach(function() {
- comb = new Comb();
- });
-
- it('Should remove units in zero-valued dimensions', function() {
- comb.configure({ 'unitless-zero': true });
- assert.equal(
- comb.processString(
- 'div { margin: 0em; padding: 0px }'
- ),
- 'div { margin: 0; padding: 0 }'
- );
- assert.equal(
- comb.processString(
- 'div { margin: 0% }'
- ),
- 'div { margin: 0 }'
- );
- });
-
- it('Should remove units in zero-valued media-query params', function() {
- comb.configure({ 'unitless-zero': true });
- assert.equal(
- comb.processString('@media all and (min-width: 0px) { div { margin: 0em; padding: 0px } }'),
- '@media all and (min-width: 0) { div { margin: 0; padding: 0 } }'
- );
- });
-
- it('Should not remove units (degs) in rotate property', function() {
- comb.configure({ 'unitless-zero': true });
- assert.equal(
- comb.processString(
- 'div { -webkit-transform: rotate(0deg); }'
- ),
- 'div { -webkit-transform: rotate(0deg); }'
- );
- });
-
-});
diff --git a/test/vendor-prefix-align.js b/test/vendor-prefix-align.js
deleted file mode 100644
index d5cf97b9..00000000
--- a/test/vendor-prefix-align.js
+++ /dev/null
@@ -1,45 +0,0 @@
-var Comb = require('../lib/csscomb');
-var fs = require('fs');
-var assert = require('assert');
-
-describe('options/vendor-prefix-align', function() {
- var comb;
-
- beforeEach(function() {
- var config = {
- 'vendor-prefix-align': true
- };
-
- comb = new Comb();
- comb.configure(config);
- });
-
- it('Should correctly align prefixes in properties', function() {
- var input = fs.readFileSync('./test/vendor-prefix-align/property-align.css', 'utf8');
- var expected = fs.readFileSync('./test/vendor-prefix-align/property-align.expected.css', 'utf8');
-
- assert.equal(comb.processString(input), expected);
- });
-
- it('Should correctly align prefixes in values', function() {
- var input = fs.readFileSync('./test/vendor-prefix-align/value-align.css', 'utf8');
- var expected = fs.readFileSync('./test/vendor-prefix-align/value-align.expected.css', 'utf8');
-
- assert.equal(comb.processString(input), expected);
- });
-
- it('Should not touch already align prefixes', function() {
- var input = fs.readFileSync('./test/vendor-prefix-align/already-aligned.css', 'utf8');
- var expected = fs.readFileSync('./test/vendor-prefix-align/already-aligned.expected.css', 'utf8');
-
- assert.equal(comb.processString(input), expected);
- });
-
- it('Should always correctly align prefixes', function() {
- var input = fs.readFileSync('./test/vendor-prefix-align/complex.css', 'utf8');
- var expected = fs.readFileSync('./test/vendor-prefix-align/complex.expected.css', 'utf8');
-
- assert.equal(comb.processString(input), expected);
- });
-
-});