diff --git a/.eslintrc b/.eslintrc index 1353aff2..236fef8c 100644 --- a/.eslintrc +++ b/.eslintrc @@ -135,8 +135,12 @@ rules: - global valid-jsdoc: - 2 - - prefer: - return: returns + - + requireParamDescription: true + requireReturnDescription: true + requireReturn: false + prefer: + return: "returns" wrap-iife: 2 yoda: - 2 diff --git a/.travis.yml b/.travis.yml index 2268ba40..a2cd1c3a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,12 +1,8 @@ language: node_js node_js: - - '0.10' - - '0.12' - '4' - '5' - node - - iojs -before_script: npm link after_success: npm run coveralls notifications: slack: diff --git a/CHANGELOG.md b/CHANGELOG.md index e0c23689..ddd13e9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,62 @@ # Sass Lint Changelog +## v1.10.0 + +**November 6th, 2016** + +The 'you can ignore those bad habits again' update + +** :tada: DISABLE LINTERS :tada: ** +The ability to enable and disable linters on the fly has finally(!) been added [#677](https://github.com/sasstools/sass-lint/pull/677) [docs](https://github.com/sasstools/sass-lint/blob/master/docs/toggle-rules-in-src.md) + +A massive thank you to everyone who commented/contributed/reported and tested this feature this was very much a community effort here. An extra special thank you to +* [@donabrams](https://github.com/donabrams) + +For his initial hard work in getting this off the ground. There were lots of others who have fixed everything from test issues to AST issues to make this possible afterwards, so thanks to you too! + +**New Features** +* `max-warnings` which is available with the sass-lint CLI is now available as an option in your config file too [#857](https://github.com/sasstools/sass-lint/pull/857) +* **New Rule** `no-url-domains` rule [#846](https://github.com/sasstools/sass-lint/pull/846) [docs](https://github.com/sasstools/sass-lint/blob/master/docs/rules/no-url-domains.md) +* **New Rule** `max-line-length` rule was added [#840](https://github.com/sasstools/sass-lint/pull/840) [docs](https://github.com/sasstools/sass-lint/blob/master/docs/rules/max-line-length.md) +* **New Rule** `max-file-line-count` rule was added [#842](https://github.com/sasstools/sass-lint/pull/842) [docs](https://github.com/sasstools/sass-lint/blob/master/docs/rules/max-file-line-count.md) +* **New Rule** `declarations-before-nesting` rule was added [#866](https://github.com/sasstools/sass-lint/pull/866) [docs](https://github.com/sasstools/sass-lint/blob/master/docs/rules/declarations-before-nesting.md) + +**Fixes** +* Fixed an issue with an un handled error being thrown in certain circumstances for the `space-before-colon` rule [#894](https://github.com/sasstools/sass-lint/pull/894) +* Operators in variable names are now handled correctly for the `variable-name-format` rule [#903](https://github.com/sasstools/sass-lint/pull/903) +* Fixed an issue with string values in the `shorthand-values` rule [#848](https://github.com/sasstools/sass-lint/pull/848) +* Fixed an issue with valid strict BEM producing an error in the `*-name-format` rules [#892](https://github.com/sasstools/sass-lint/pull/892) +* Fixed an issue with non-string user conventions in the `border-zero` rule [#913](https://github.com/sasstools/sass-lint/pull/913) +* Fixed an issue where BOM markers in files were causing parse errors or random errors/warnings [#893](https://github.com/sasstools/sass-lint/pull/893) +* Fixed an issue with interpolates properties in the `no-duplicate-properties` rule [#915](https://github.com/sasstools/sass-lint/pull/915) +* Fixed a possible error with invalid user conventions in the `border-zero` rule [#926](https://github.com/sasstools/sass-lint/pull/926) + +**Changes** +* Node 0.10 and 0.12 are no longer officially supported by sass-lint. We've not deliberately broken these builds but we will no longer be testing against them either [#896](https://github.com/sasstools/sass-lint/issues/896) & [#924](https://github.com/sasstools/sass-lint/pull/924) +* In future the `no-url-protocols` rule will not lint domains in URL's for now a new flag is added to mimic this behaviour. The new `no-url-domains` rule can be used instead [#813](https://github.com/sasstools/sass-lint/issues/813) +* Front matter such as those present in Jekyll templates will now be ignored in all files before passing to the AST / Linting [897](https://github.com/sasstools/sass-lint/pull/897) +* Running the tests no longer required sass-lint development to be `npm-link`ed or globally installed. [#911](https://github.com/sasstools/sass-lint/pull/911) +* The concentric property list in `property-sort-order` was updated to reflect the latest release [#922](https://github.com/sasstools/sass-lint/pull/922) + +**Updates** +* AST fixes have arrived with version 3.4.7 of gonzales-pe [#906](https://github.com/sasstools/sass-lint/pull/906) +* Updated to the latest versions of many other packages + +**Documentation** +* The documentation around configuring a rule was tidied up and made clearer [#910](https://github.com/sasstools/sass-lint/pull/910) + +**Special thanks to** + +* [bgriffith](https://github.com/bgriffith) +* [donabrams](https://github.com/donabrams) +* [danpurdy](https://github.com/DanPurdy) +* [danwaz](https://github.com/danwaz) +* [lucasjahn](https://github.com/lucasjahn) +* [mrjamesriley](https://github.com/mrjamesriley) +* [notrobin](https://github.com/nottrobin) +* [onishiweb](https://github.com/onishiweb) +* [richarddewit](https://github.com/richarddewit) + ## v1.9.1 **August 25, 2016** diff --git a/README.md b/README.md index aab7434a..0d3cad35 100644 --- a/README.md +++ b/README.md @@ -79,19 +79,97 @@ For all [rules](https://github.com/sasstools/sass-lint/tree/master/docs/rules), If you want to configure options, set the rule to an array, where the first item in the array is the severity, and the second item in the array is an object including the options you would like to set. -An example configuration of a rule with options look like the following: +Here is an example configuration of a rule, where we are specifying that breaking the [indentation rule](https://github.com/sasstools/sass-lint/blob/master/docs/rules/indentation.md) should be treated as an error (its severity set to two), and setting the `size` option of the rule to 2 spaces: ```yml -indentation: - - 2 - - - size: 2 +rules: + indentation: + - 2 + - + size: 2 ``` ### [Rules Documentation](https://github.com/sasstools/sass-lint/tree/master/docs/rules) --- +## Disabling Linters via Source + +Special comments can be used to disable and enable certain rules throughout your source files in a variety of scenarios. These can be useful when dealing with legacy code or with certain necessary code smells. You can read the documentation for this feature [here](https://github.com/sasstools/sass-lint/tree/master/docs/toggle-rules-in-src.md). + +Below are examples of how to use this feature: + + +### Disable a rule for the entire file + +```scss +// sass-lint:disable border-zero +p { + border: none; // No lint reported +} +``` + +### Disable more than 1 rule + +```scss +// sass-lint:disable border-zero, quotes +p { + border: none; // No lint reported + content: "hello"; // No lint reported +} +``` + +### Disable a rule for a single line + +```scss +p { + border: none; // sass-lint:disable-line border-zero +} +``` + +### Disable all lints within a block (and all contained blocks) + +```scss +p { + // sass-lint:disable-block border-zero + border: none; // No result reported +} + +a { + border: none; // Failing result reported +} +``` + +### Disable and enable again + +```scss +// sass-lint:disable border-zero +p { + border: none; // No result reported +} +// sass-lint:enable border-zero + +a { + border: none; // Failing result reported +} +``` + +### Disable/enable all linters + +```scss +// sass-lint:disable-all +p { + border: none; // No result reported +} +// sass-lint:enable-all + +a { + border: none; // Failing result reported +} +``` + +--- + ## CLI Sass Lint [`v1.1.0`](https://github.com/sasstools/sass-lint/releases/tag/v1.1.0) introduced the ability to run Sass Lint through a command line interface. See the [CLI Docs](https://github.com/sasstools/sass-lint/tree/master/docs/cli) for full documentation on how to use the CLI. @@ -169,6 +247,24 @@ For further information you can visit our CLI documentation linked below. --- +## Front matter + +Certain static site generators such as [Jekyll](http://jekyllrb.com/docs/frontmatter/) include the YAML front matter block at the top of their scss file. Sass-lint by default checks a file for this block and attempts to parse your Sass without this front matter. You can see an example of a front matter block below. + +```scss + +--- +# Only the main Sass file needs front matter (the dashes are enough) +--- + +.test { + color: red; +} + +``` + +--- + ## Contributions We welcome all contributions to this project but please do read our [contribution guidelines](https://github.com/sasstools/sass-lint/blob/master/CONTRIBUTING.md) first, especially before opening a pull request. It would also be good to read our [code of conduct](https://github.com/sasstools/sass-lint/blob/master/CODE_OF_CONDUCT.md). diff --git a/appveyor.yml b/appveyor.yml index 22ade15c..6b46be8f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -5,18 +5,10 @@ init: # Test against this version of Node.js environment: matrix: - - nodejs_version: '0.10' - - nodejs_version: '0.12' - # io.js - - nodejs_version: '1' - nodejs_version: '4' - nodejs_version: '5' # Latest stable version of Node - nodejs_version: 'stable' -matrix: - # Help instructions and version number don't show in appveyor - allow_failures: - - nodejs_version: '0.10' # Install scripts. (runs after repo cloning) install: @@ -24,8 +16,6 @@ install: - ps: Install-Product node $env:nodejs_version # install modules - npm install - # link - - npm link # Post-install test scripts. test_script: diff --git a/bin/sass-lint.js b/bin/sass-lint.js index 787e9330..a6218b8f 100755 --- a/bin/sass-lint.js +++ b/bin/sass-lint.js @@ -6,15 +6,7 @@ var program = require('commander'), lint = require('../index'); var configPath, - ignores, - configOptions = {}, - exitCode = 0; - -var tooManyWarnings = function (detects) { - var warningCount = lint.warningCount(detects).count; - - return warningCount > 0 && warningCount > program.maxWarnings; -}; + configOptions = {}; var detectPattern = function (pattern) { var detects; @@ -25,13 +17,7 @@ var detectPattern = function (pattern) { lint.outputResults(detects, configOptions, configPath); } - if (lint.errorCount(detects).count || tooManyWarnings(detects)) { - exitCode = 1; - } - - if (program.exit) { - lint.failOnError(detects); - } + lint.failOnError(detects, configOptions, configPath); }; program @@ -47,21 +33,16 @@ program .option('--max-warnings [integer]', 'Number of warnings to trigger nonzero exit code') .parse(process.argv); +// Create "options" and "files" dictionaries if they don't exist +configOptions.files = configOptions.files || {}; +configOptions.options = configOptions.options || {}; if (program.config && program.config !== true) { configPath = program.config; } if (program.ignore && program.ignore !== true) { - ignores = program.ignore.split(', '); - if (configOptions.hasOwnProperty('files')) { - configOptions.files.ignore = ignores; - } - else { - configOptions.files = { - 'ignore': ignores - }; - } + configOptions.files.ignore = program.ignore.split(', '); } if (program.syntax && ['sass', 'scss'].indexOf(program.syntax) > -1) { @@ -69,25 +50,15 @@ if (program.syntax && ['sass', 'scss'].indexOf(program.syntax) > -1) { } if (program.format && program.format !== true) { - if (configOptions.hasOwnProperty('options')) { - configOptions.options.formatter = program.format; - } - else { - configOptions.options = { - 'formatter': program.format - }; - } + configOptions.options.formatter = program.format; } if (program.output && program.output !== true) { - if (configOptions.hasOwnProperty('options')) { - configOptions.options['output-file'] = program.output; - } - else { - configOptions.options = { - 'output-file': program.output - }; - } + configOptions.options['output-file'] = program.output; +} + +if (program.maxWarnings && program.maxWarnings !== true) { + configOptions.options['max-warnings'] = program.maxWarnings; } if (program.args.length === 0) { @@ -98,7 +69,3 @@ else { detectPattern(path); }); } - -process.on('exit', function () { - process.exit(exitCode); // eslint-disable-line no-process-exit -}); diff --git a/docs/options/max-warnings.md b/docs/options/max-warnings.md new file mode 100644 index 00000000..32d6e8ed --- /dev/null +++ b/docs/options/max-warnings.md @@ -0,0 +1,29 @@ +# Max warnings + +An error will be thrown if the total number of warnings exceeds the `max-warnings` setting. + +## Examples + +This can be set as a command-line option: + +``` bash +$ sass-lint --max-warnings 50 +``` + +In `.sass-lint.yml`: + +``` yaml +options: + max-warnings: 50 +``` + +Or inside a script: + +``` javascript +var sassLint = require('sass-lint'), + config = {options: {'max-warnings': 50}}; + +results = sassLint.lintFiles('sass/**/*.scss', config) +sassLint.failOnError(results, config); +``` + diff --git a/docs/rules/border-zero.md b/docs/rules/border-zero.md index a1377b7f..dcd9e043 100644 --- a/docs/rules/border-zero.md +++ b/docs/rules/border-zero.md @@ -6,6 +6,8 @@ Rule `border-zero` will enforce whether one should use `0` or `none` when specif * `convention`: `'0'`/`'none'` (defaults to `0`) +> If an invalid convention is provided the rule will default back to `convention: '0'`. An extra warning/error will also be thrown on `line 1` `column 1` of a file with a lint issue to inform you of this fact. + ## Examples When `convention: '0'`, the following are allowed. When `convention: 'none'`, the following are disallowed: @@ -31,3 +33,17 @@ When `convention: 'none'`, the following are allowed. When `convention: '0'`, th border-left: none; } ``` + +### Invalid conventions + +When the invalid convention `convention: 'zero'` is supplied, the following are allowed as the rule defaults to `convention: '0'`. + +```scss +.foo { + border: none; +} + +.bar { + border-left: 0; +} +``` diff --git a/docs/rules/class-name-format.md b/docs/rules/class-name-format.md index 45cf6b59..0a3fb285 100644 --- a/docs/rules/class-name-format.md +++ b/docs/rules/class-name-format.md @@ -191,6 +191,10 @@ When enabled, the following are allowed: .owner-name_mod-name_mod-val { content: ''; } + +.block-name__elem-name_mod-bool { + content: ''; +} ``` When enabled, the following are disallowed: diff --git a/docs/rules/declarations-before-nesting.md b/docs/rules/declarations-before-nesting.md new file mode 100644 index 00000000..d68aebcd --- /dev/null +++ b/docs/rules/declarations-before-nesting.md @@ -0,0 +1,29 @@ +# Declarations Before Nesting + +Rule `declarations-before-nesting` will enforce that declarations should be written before nesting in a ruleset. + +## Examples + +When enabled, the following are allowed: + +```scss +.foo { + content: 'baz'; + + .bar { + content: 'qux'; + } +} +``` + +When enabled, the following are disallowed: + +```scss +.foo { + .bar { + content: 'qux'; + } + + content: 'baz'; +} +``` diff --git a/docs/rules/max-file-line-count.md b/docs/rules/max-file-line-count.md new file mode 100644 index 00000000..c588a461 --- /dev/null +++ b/docs/rules/max-file-line-count.md @@ -0,0 +1,27 @@ +# Max File Line Count + +Rule `max-file-line-count` will enforce that a file's length doesn't exceed a certain number of lines + +## Options + +* `length`: `number`, (defaults to 300) + +## Examples + +When enabled, the following are disallowed: + +```scss +/* +* line count is represented along the +* left hand side of the following example +*/ + 1| .test { + 2| color: red + 3| } +===== +~ snip ~ +===== +299| .bar { +300| color: blue; +301| } +``` diff --git a/docs/rules/max-line-length.md b/docs/rules/max-line-length.md new file mode 100644 index 00000000..9e0b3aad --- /dev/null +++ b/docs/rules/max-line-length.md @@ -0,0 +1,24 @@ +# Max Line Length + +Rule `max-line-length` will enforce that lines do not exceed a max length / limit. + +## Options + +* `length`: `number`, (defaults to 80) + +## Examples + +When enabled, the following are disallowed: + +```scss +.really--long--class-name--that-unfortunately--isnt--very--succint--and-looks-stupid { + color: red; +} + +// ============================================================================== +// +// This comment is too long clearly, we should probably make sure we have a rule to +// determine when we breach this length +// +// ============================================================================== +``` diff --git a/docs/rules/no-url-domains.md b/docs/rules/no-url-domains.md new file mode 100644 index 00000000..8c536b1a --- /dev/null +++ b/docs/rules/no-url-domains.md @@ -0,0 +1,37 @@ +# No Url Domains + +Rule `no-url-domains` will enforce that domains are not used within urls. + +## Examples + +When enabled, the following are allowed: + +```scss +.foo { + background-image: url('/img/bar.png'); +} + +.foo { + background-image: url('img/bar.png'); +} + +.foo { + background-image: url('bar.png'); +} +``` + +When enabled, the following are disallowed: + +```scss +.foo { + background-image: url('https://foo.com/img/bar.png'); +} + +.foo { + background-image: url('http://foo.com/img/bar.png'); +} + +.foo { + background-image: url('//foo.com/img/bar.png'); +} +``` diff --git a/docs/rules/no-url-protocols.md b/docs/rules/no-url-protocols.md index ebf620b3..50baa5ff 100644 --- a/docs/rules/no-url-protocols.md +++ b/docs/rules/no-url-protocols.md @@ -2,9 +2,17 @@ Rule `no-url-protocols` will enforce that protocols and domains are not used within urls. +## Options + +* `allow-protocol-relative-urls`: `true`/`false` (defaults to `false`) +> This option is scheduled to be deprecated in favour of the [no-url-domains](https://github.com/sasstools/sass-lint/blob/develop/docs/rules/no-url-domains.md) rule in sass-lint 2.0. + ## Examples -When enabled, the following are allowed: +### `allow-protocol-relative-urls` + + +When `allow-protocol-relative-urls: false`, the following are allowed: ```scss .foo { @@ -20,7 +28,7 @@ When enabled, the following are allowed: } ``` -When enabled, the following are disallowed: +When `allow-protocol-relative-urls: false`, the following are disallowed: ```scss .foo { @@ -35,3 +43,35 @@ When enabled, the following are disallowed: background-image: url('//foo.com/img/bar.png'); } ``` + +When `allow-protocol-relative-urls: true`, the following are allowed: + +```scss +.foo { + background-image: url('//foo.com/img/bar.png'); +} + +.foo { + background-image: url('/img/bar.png'); +} + +.foo { + background-image: url('img/bar.png'); +} + +.foo { + background-image: url('bar.png'); +} +``` + +When `allow-protocol-relative-urls: true`, the following are disallowed: + +```scss +.foo { + background-image: url('https://foo.com/img/bar.png'); +} + +.foo { + background-image: url('http://foo.com/img/bar.png'); +} +``` diff --git a/docs/sass-lint.yml b/docs/sass-lint.yml index 3de8a760..682ec6c5 100644 --- a/docs/sass-lint.yml +++ b/docs/sass-lint.yml @@ -9,6 +9,8 @@ options: formatter: html # Output file instead of logging results output-file: 'linters/sass-lint.html' + # Raise an error if more than 50 warnings are generated + max-warnings: 50 # File Options files: include: 'sass/**/*.s+(a|c)ss' diff --git a/docs/toggle-rules-in-src.md b/docs/toggle-rules-in-src.md new file mode 100644 index 00000000..05e5ba78 --- /dev/null +++ b/docs/toggle-rules-in-src.md @@ -0,0 +1,71 @@ +# Toggling Rules Inside Source Files + +For special cases where a particular lint doesn't make sense in a specific area of a file, special inline comments can be used to enable/disable linters. Some examples are provided below: + +## Disable a rule for the entire file + +```scss +// sass-lint:disable border-zero +p { + border: none; // No lint reported +} +``` + +## Disable more than 1 rule + +```scss +// sass-lint:disable border-zero, quotes +p { + border: none; // No lint reported + content: "hello"; // No lint reported +} +``` + +## Disable a rule for a single line + +```scss +p { + border: none; // sass-lint:disable-line border-zero +} +``` + +## Disable all lints within a block (and all contained blocks) + +```scss +p { + // sass-lint:disable-block border-zero + border: none; // No result reported +} + +a { + border: none; // Failing result reported +} +``` + +## Disable and enable again + +```scss +// sass-lint:disable border-zero +p { + border: none; // No result reported +} +// sass-lint:enable border-zero + +a { + border: none; // Failing result reported +} +``` + +## Disable/enable all linters + +```scss +// sass-lint:disable-all +p { + border: none; // No result reported +} +// sass-lint:enable-all + +a { + border: none; // Failing result reported +} +``` diff --git a/index.js b/index.js index decbc923..f3ff5b5f 100644 --- a/index.js +++ b/index.js @@ -2,14 +2,19 @@ var slConfig = require('./lib/config'), groot = require('./lib/groot'), + exceptions = require('./lib/exceptions'), helpers = require('./lib/helpers'), slRules = require('./lib/rules'), + ruleToggler = require('./lib/ruleToggler'), glob = require('glob'), path = require('path'), fs = require('fs-extra'), globule = require('globule'); -var sassLint = function (config) { +var getToggledRules = ruleToggler.getToggledRules, + isResultEnabled = ruleToggler.isResultEnabled; + +var sassLint = function (config) { // eslint-disable-line no-unused-vars config = require('./lib/config')(config); return; }; @@ -101,7 +106,9 @@ sassLint.lintText = function (file, options, configPath) { detects, results = [], errors = 0, - warnings = 0; + warnings = 0, + ruleToggles = null, + isEnabledFilter = null; try { ast = groot(file.text, file.format, file.filename); @@ -120,8 +127,12 @@ sassLint.lintText = function (file, options, configPath) { } if (ast.content && ast.content.length > 0) { + ruleToggles = getToggledRules(ast); + isEnabledFilter = isResultEnabled(ruleToggles); + rules.forEach(function (rule) { - detects = rule.rule.detect(ast, rule); + detects = rule.rule.detect(ast, rule) + .filter(isEnabledFilter); results = results.concat(detects); if (detects.length) { if (rule.severity === 1) { @@ -284,14 +295,30 @@ sassLint.outputResults = function (results, options, configPath) { * Throws an error if there are any errors detected. The error includes a count of all errors * and a list of all files that include errors. * - * @param {object} results our results object + * @param {object} results - our results object + * @param {object} [options] - extra options to use when running failOnError, e.g. max-warnings + * @param {string} [configPath] - path to the config file * @returns {void} */ -sassLint.failOnError = function (results) { - var errorCount = this.errorCount(results); +sassLint.failOnError = function (results, options, configPath) { + // Default parameters + options = typeof options !== 'undefined' ? options : {}; + configPath = typeof configPath !== 'undefined' ? configPath : null; + + var errorCount = this.errorCount(results), + warningCount = this.warningCount(results), + configOptions = this.getConfig(options, configPath).options; if (errorCount.count > 0) { - throw new Error(errorCount.count + ' errors were detected in \n- ' + errorCount.files.join('\n- ')); + throw new exceptions.SassLintFailureError(errorCount.count + ' errors were detected in \n- ' + errorCount.files.join('\n- ')); + } + + if (!isNaN(configOptions['max-warnings']) && warningCount.count > configOptions['max-warnings']) { + throw new exceptions.MaxWarningsExceededError( + 'Number of warnings (' + warningCount.count + + ') exceeds the allowed maximum of ' + configOptions['max-warnings'] + + '.\n' + ); } }; diff --git a/lib/config/property-sort-orders/concentric.yml b/lib/config/property-sort-orders/concentric.yml index 5fb6fae5..b66cfa3e 100644 --- a/lib/config/property-sort-orders/concentric.yml +++ b/lib/config/property-sort-orders/concentric.yml @@ -11,6 +11,19 @@ order: - 'bottom' - 'left' + - 'flex' + - 'flex-basis' + - 'flex-direction' + - 'flex-flow' + - 'flex-grow' + - 'flex-shrink' + - 'flex-wrap' + - 'align-content' + - 'align-items' + - 'align-self' + - 'justify-content' + - 'order' + - 'columns' - 'column-gap' - 'column-fill' @@ -46,6 +59,10 @@ order: - 'margin-left' - 'outline' + - 'outline-offset' + - 'outline-width' + - 'outline-style' + - 'outline-color' - 'border' - 'border-top' @@ -79,6 +96,8 @@ order: - 'box-shadow' - 'background' + - 'background-attachment' + - 'background-clip' - 'background-color' - 'background-image' - 'background-repeat' diff --git a/lib/config/sass-lint.yml b/lib/config/sass-lint.yml index 1b3ed511..3298df52 100644 --- a/lib/config/sass-lint.yml +++ b/lib/config/sass-lint.yml @@ -38,12 +38,14 @@ rules: no-trailing-zero: 1 no-transition-all: 1 no-universal-selectors: 0 + no-url-domains: 1 no-url-protocols: 1 no-vendor-prefixes: 1 no-warn: 1 property-units: 0 # Nesting + declarations-before-nesting: 1 force-attribute-nesting: 1 force-element-nesting: 1 force-pseudo-nesting: 1 @@ -67,6 +69,8 @@ rules: hex-notation: 1 indentation: 1 leading-zero: 1 + max-line-length: 0 + max-file-line-count: 0 nesting-depth: 1 property-sort-order: 1 pseudo-element: 1 diff --git a/lib/exceptions.js b/lib/exceptions.js new file mode 100644 index 00000000..77e15683 --- /dev/null +++ b/lib/exceptions.js @@ -0,0 +1,19 @@ +'use strict'; + +var util = require('util'); + +module.exports = { + SassLintFailureError: function (message) { + Error.captureStackTrace(this, this.constructor); + this.name = 'SassLintFailureError'; + this.message = message; + }, + MaxWarningsExceededError: function (message) { + Error.captureStackTrace(this, this.constructor); + this.name = 'MaxWarningsExceededError'; + this.message = message; + } +}; + +util.inherits(module.exports.SassLintFailureError, Error); +util.inherits(module.exports.MaxWarningsExceededError, Error); diff --git a/lib/groot.js b/lib/groot.js index f3bad09e..1d2ee529 100644 --- a/lib/groot.js +++ b/lib/groot.js @@ -4,12 +4,19 @@ 'use strict'; var gonzales = require('gonzales-pe'); +var fm = require('front-matter'); +var helpers = require('./helpers'); module.exports = function (text, syntax, filename) { var tree; // Run `.toString()` to allow Buffers to be passed in - text = text.toString(); + text = helpers.stripBom(text.toString()); + + // if we're skipping front matter do it here, fall back to just our text in case it fails + if (fm.test(text)) { + text = fm(text).body || text; + } try { tree = gonzales.parse(text, { diff --git a/lib/helpers.js b/lib/helpers.js index 236126a0..0b9af3ce 100644 --- a/lib/helpers.js +++ b/lib/helpers.js @@ -169,7 +169,7 @@ helpers.isSnakeCase = function (str) { * @returns {boolean} Whether str adheres to strict-BEM format */ helpers.isStrictBEM = function (str) { - return /^[a-z](\-?[a-z0-9]+)*(__[a-z0-9](\-?[a-z0-9]+)*)?((_[a-z0-9](\-?[a-z0-9]+)*){2})?$/.test(str); + return /^[a-z](\-?[a-z0-9]+)*(__[a-z0-9](\-?[a-z0-9]+)*)?((_[a-z0-9](\-?[a-z0-9]+)*){0,2})?$/.test(str); }; /** @@ -394,4 +394,24 @@ helpers.isPartialStringMatch = function (needle, haystack) { return false; }; +/** + * A copy of the the stripBom module from https://github.com/sindresorhus/strip-bom/blob/master/index.js + * The module requires node > 4 whereas we support earlier versions. + * This function strips the BOM marker from the beginning of a file + * + * @param {string} str - The string we wish to strip the BOM marker from + * @returns {string} The string without a BOM marker + */ +helpers.stripBom = function (str) { + if (typeof str !== 'string') { + throw new TypeError('Expected a string, got ' + typeof str); + } + + if (str.charCodeAt(0) === 0xFEFF) { + return str.slice(1); + } + + return str; +}; + module.exports = helpers; diff --git a/lib/ruleToggler.js b/lib/ruleToggler.js new file mode 100644 index 00000000..f4bbd520 --- /dev/null +++ b/lib/ruleToggler.js @@ -0,0 +1,249 @@ +'use strict'; + +/** + * Adds each rule in our array of rules in a disable comment into the toggledRules object + * under the correct rule name along with the line and column number where the disable comment + * was encountered + * + * @param {Object} toggledRules - Contains the information about each rule disable/enable + encountered and and what line/column it occurred on + * @param {Array} rules - An array of rule names + * @param {number} line - The line number the disable appeared on + * @param {number} column - The column number the disable appeared on + */ +var addDisable = function (toggledRules, rules, line, column) { + rules.map(function (rule) { + toggledRules.ruleEnable[rule] = toggledRules.ruleEnable[rule] || []; + toggledRules.ruleEnable[rule].push([false, line, column]); + }); +}; + +/** + * Adds each rule in our array of rules in a enable comment into the toggledRules object + * under the correct rule name along with the line and column number where the enable comment + * was encountered + * + * @param {Object} toggledRules - Contains the information about each rule enable + encountered and and what line/column it occurred on + * @param {Array} rules - An array of rule names + * @param {number} line - The line number the enable appeared on + * @param {number} column - The column number the enable appeared on + */ +var addEnable = function (toggledRules, rules, line, column) { + rules.map(function (rule) { + toggledRules.ruleEnable[rule] = toggledRules.ruleEnable[rule] || []; + toggledRules.ruleEnable[rule].push([true, line, column]); + }); +}; + +/** + * Adds each rule in our array of rules in a disable block comment into the toggledRules object + * under the correct rule name along with the line and column number where the disable block comment + * was encountered + * + * @param {Object} toggledRules - Contains the information about each rule enable + encountered and and what line/column that block occurred on + * @param {Array} rules - An array of rule names + * @param {Object} block - The block that is to be disabled + */ +var addDisableBlock = function (toggledRules, rules, block) { + rules.map(function (rule) { + toggledRules.ruleEnable[rule] = toggledRules.ruleEnable[rule] || []; + toggledRules.ruleEnable[rule].push([false, block.start.line, block.start.column]); + toggledRules.ruleEnable[rule].push([true, block.end.line, block.end.column]); + }); +}; + +/** + * Adds a globally disabled flag to the toggled rules globalEnable property including the line and column + * that this comment was encountered on. + * + * @param {Object} toggledRules - Contains the information about the global disable comment + encountered and and what line/column it occurred on + * @param {number} line - The line number the disable appeared on + * @param {number} column - The column number the disable appeared on + */ +var addDisableAll = function (toggledRules, line, column) { + toggledRules.globalEnable + .push([false, line, column]); +}; + +/** + * Adds a globally enabled flag to the toggled rules globalEnable property including the line and column + * that this comment was encountered on. + * + * @param {Object} toggledRules - Contains the information about the global enable comment + encountered and and what line/column it occurred on + * @param {number} line - The line number the enable appeared on + * @param {number} column - The column number the enable appeared on + */ +var addEnableAll = function (toggledRules, line, column) { + toggledRules.globalEnable + .push([true, line, column]); +}; + +/** + * Adds a line disabled flag to the ruleEnable property of the toggledRules object for each rule name + * encountered in the comment and which line this comment was discovered on / refers to + * + * @param {Object} toggledRules - Contains the information about the line disable comment encountered, the rules + * it relates to and which line it was encountered on + * @param {Array} rules - An array of rule names to apply + * @param {number} line - The line number that this disable should refer to + */ +var addDisableLine = function (toggledRules, rules, line) { + rules.map(function (rule) { + toggledRules.ruleEnable[rule] = toggledRules.ruleEnable[rule] || []; + // NOTE: corner case not handled here: a 2nd disable inside an ignored line, which is unrealistically pathological. + toggledRules.ruleEnable[rule].push([false, line, 1]); + toggledRules.ruleEnable[rule].push([true, line + 1, 1]); + }); +}; + +/** + * This is the sorting function we use to sort the toggle stacks in our getToggledRules method + * First sorts by line and then by column if the lines are identical + * + * @param {Array} toggleRangeA - The first rule to sort + * @param {Array} toggleRangeB - The second rule to sort + * + * @returns {number} A pointer to signify to the sort method how the currently in focus value should be sorted + */ +var sortRange = function (toggleRangeA, toggleRangeB) { + var aLine = toggleRangeA[1], + aCol = toggleRangeA[2], + bLine = toggleRangeB[1], + bCol = toggleRangeB[2]; + if (aLine < bLine) { + return -1; + } + if (aLine > bLine) { + return 1; + } + if (aCol < bCol) { + return -1; + } + if (aCol > bCol) { + return 1; + } + return 0; +}; + +/** + * Checks if line number A is before line number B, if it's the same then it checks if the column of A + * is before the column of B + * + * @param {number} x - The line number of A + * @param {number} y - The column number of A + * @param {number} x2 - The line number of B + * @param {number} y2 - The column number of B + * + * @returns {Boolean} Whether the current line/column A is before or the same as B + */ +var isBeforeOrSame = function (x, y, x2, y2) { + return x < x2 || (x === x2 && y < y2); +}; + +/** + * Traverses the AST looking for sass-lint disable/enable comments and then builds an Object/node representation + * of any it encounters + * + * @param {Object} ast - Gonzales PE abstract syntax tree + * + * @returns {Object} The toggledRules object containing all of our rule enable/disable information + */ +module.exports.getToggledRules = function (ast) { + var toggledRules = { + ruleEnable: { + // Format in here is [isEnabled, line, column] + }, + globalEnable: [] + }; + if (!ast.traverseByTypes) { + return toggledRules; + } + ast.traverseByTypes(['multilineComment', 'singlelineComment'], function (comment, i, parent) { + var content = comment.content; + if (!content) { + return; + } + var tokens = content.split(/[\s,]+/) + .filter(function (s) { + return s.trim().length > 0; + }); + if (!tokens.length) { + return; + } + var first = tokens[0], + rules = tokens.slice(1); + switch (first) { + case 'sass-lint:disable': + addDisable(toggledRules, rules, comment.start.line, comment.start.column); + break; + case 'sass-lint:enable': + addEnable(toggledRules, rules, comment.start.line, comment.start.column); + break; + case 'sass-lint:disable-block': + // future ref: not sure what the appropriate behavior is if there is no parent block; currently NPEs + addDisableBlock(toggledRules, rules, parent); + break; + case 'sass-lint:disable-all': + addDisableAll(toggledRules, comment.start.line, comment.start.column); + break; + case 'sass-lint:enable-all': + addEnableAll(toggledRules, comment.start.line, comment.start.column); + break; + case 'sass-lint:disable-line': + addDisableLine(toggledRules, rules, comment.start.line); + break; + default: + return; + } + }); + // Sort these toggle stacks so reading them is easier (algorithmically). + // Usually already sorted but since it's not guaranteed by the contract with gonzales-pe, ensuring it is. + toggledRules.globalEnable.sort(sortRange); + Object.keys(toggledRules.ruleEnable).map(function (ruleId) { + toggledRules.ruleEnable[ruleId].sort(sortRange); + }); + return toggledRules; +}; + +/** + * Filters our rule results by checking the lint result and its line/column against our + * toggledRules object to see whether we should still be reporting this lint. + * + * @param {Object} toggledRules - The toggledRules object containing all of our rule enable/disable information + * + * @returns {Boolean} Whether the current rule is disabled for this lint report + */ +module.exports.isResultEnabled = function (toggledRules) { + return function (ruleResult) { + var ruleId = ruleResult.ruleId; + // Convention: if no column or line, assume rule is targetting 1. + var line = ruleResult.line || 1; + var column = ruleResult.column || 1; + var isGloballyEnabled = toggledRules.globalEnable + .reduce(function (acc, toggleRange) { + return isBeforeOrSame(line, column, toggleRange[1], toggleRange[2]) + ? acc + : toggleRange[0]; + }, true); + if (!isGloballyEnabled) { + return false; + } + if (!toggledRules.ruleEnable[ruleId]) { + return true; + } + var isRuleEnabled = toggledRules.ruleEnable[ruleId] + .reduce(function (acc, toggleRange) { + return isBeforeOrSame(line, column, toggleRange[1], toggleRange[2]) + ? acc + : toggleRange[0]; + }, true); + if (!isRuleEnabled) { + return false; + } + return true; + }; +}; diff --git a/lib/rules/border-zero.js b/lib/rules/border-zero.js index 9fdc9d74..ab208902 100644 --- a/lib/rules/border-zero.js +++ b/lib/rules/border-zero.js @@ -3,6 +3,7 @@ var helpers = require('../helpers'); var borders = ['border', 'border-top', 'border-right', 'border-bottom', 'border-left']; +var allowedConventions = ['0', 'none']; module.exports = { 'name': 'border-zero', @@ -11,6 +12,11 @@ module.exports = { }, 'detect': function (ast, parser) { var result = []; + var userConvention = parser.options.convention.toString(); + var convention = allowedConventions.indexOf(userConvention) !== -1 + ? userConvention + : allowedConventions[0]; + var invalidConvention = convention !== userConvention; ast.traverseByType('declaration', function (declaration) { var isBorder = false; @@ -29,12 +35,22 @@ module.exports = { var node = item.content[0]; if (node.type === 'number' || node.type === 'ident') { if (node.content === '0' || node.content === 'none') { - if (parser.options.convention !== node.content) { + if (convention !== node.content) { + if (invalidConvention) { + invalidConvention = false; + result = helpers.addUnique(result, { + 'ruleId': parser.rule.name, + 'line': 1, + 'column': 1, + 'message': 'The border-zero convention `' + userConvention + ' in your config file is not valid. Defaulted to convention \'0\'', + 'severity': parser.severity + }); + } result = helpers.addUnique(result, { 'ruleId': parser.rule.name, 'line': node.start.line, 'column': node.start.column, - 'message': 'A value of `' + node.content + '` is not allowed. `' + parser.options.convention + '` must be used.', + 'message': 'A value of `' + node.content + '` is not allowed. `' + convention + '` must be used.', 'severity': parser.severity }); } diff --git a/lib/rules/declarations-before-nesting.js b/lib/rules/declarations-before-nesting.js new file mode 100644 index 00000000..992cad24 --- /dev/null +++ b/lib/rules/declarations-before-nesting.js @@ -0,0 +1,47 @@ +'use strict'; + +var helpers = require('../helpers'); + +module.exports = { + 'name': 'declarations-before-nesting', + 'defaults': {}, + 'detect': function (ast, parser) { + var result = [], + error; + + ast.traverseByType('block', function (block) { + if (block.contains('ruleset') && block.contains('declaration')) { + var rulesetIndex; + + block.forEach(function (item, j) { + var declarationIndex; + var declaration; + + if (item.is('ruleset') && rulesetIndex === void 0) { + rulesetIndex = j; + } + + if (item.is('declaration')) { + declarationIndex = j; + declaration = item; + } + + if (rulesetIndex < declarationIndex && declaration) { + error = { + 'ruleId': parser.rule.name, + 'line': declaration.start.line, + 'column': declaration.start.column, + 'message': 'Declarations should come before nestings', + 'severity': parser.severity + }; + result = helpers.addUnique(result, error); + } + }); + + rulesetIndex = null; + } + }); + + return result; + } +}; diff --git a/lib/rules/max-file-line-count.js b/lib/rules/max-file-line-count.js new file mode 100644 index 00000000..5cc08125 --- /dev/null +++ b/lib/rules/max-file-line-count.js @@ -0,0 +1,25 @@ +'use strict'; + +var helpers = require('../helpers'); + +module.exports = { + 'name': 'max-file-line-count', + 'defaults': { + length: 300 + }, + 'detect': function (ast, parser) { + var result = []; + + if (ast.end.line > parser.options.length) { + result = helpers.addUnique(result, { + 'ruleId': parser.rule.name, + 'line': ast.end.line, + 'column': 0, + 'message': 'This file has ' + ast.end.line + ' lines, which exceeds the maximum of ' + parser.options.length + ' lines allowed.', + 'severity': parser.severity + }); + } + + return result; + } +}; diff --git a/lib/rules/max-line-length.js b/lib/rules/max-line-length.js new file mode 100644 index 00000000..2c447294 --- /dev/null +++ b/lib/rules/max-line-length.js @@ -0,0 +1,32 @@ +'use strict'; + +var helpers = require('../helpers'); + +module.exports = { + 'name': 'max-line-length', + 'defaults': { + length: 80 + }, + 'detect': function (ast, parser) { + var result = []; + + ast.traverseByType('space', function (space) { + var lineLength = 0; + if (helpers.hasEOL(space.content)) { + lineLength = space.start.column - 1; + } + + if (lineLength > parser.options.length) { + result = helpers.addUnique(result, { + 'ruleId': parser.rule.name, + 'severity': parser.severity, + 'line': space.start.line, + 'column': 0, + 'message': 'line ' + space.start.line + ' exceeds the maximum line length of ' + parser.options.length + }); + } + }); + + return result; + } +}; diff --git a/lib/rules/no-duplicate-properties.js b/lib/rules/no-duplicate-properties.js index 1302a77b..ad65f43a 100644 --- a/lib/rules/no-duplicate-properties.js +++ b/lib/rules/no-duplicate-properties.js @@ -1,6 +1,7 @@ 'use strict'; var helpers = require('../helpers'); +var selectorHelpers = require('../selector-helpers'); module.exports = { 'name': 'no-duplicate-properties', @@ -25,8 +26,12 @@ module.exports = { warnMessage = false; declaration.eachFor('property', function (item) { - var property = item.content[0].content; - + var property = ''; + item.content.forEach(function (subItem) { + // Although not a selector the method here helps us construct the proper property name + // taking into account any interpolation etc + property += selectorHelpers.constructSelector(subItem); + }); if (properties.indexOf(property) !== -1 && properties.length >= 1) { if (parser.options.exclude.indexOf(property) !== -1 && properties[properties.length - 1] !== property) { warnMessage = 'Excluded duplicate properties must directly follow each other.'; diff --git a/lib/rules/no-url-domains.js b/lib/rules/no-url-domains.js new file mode 100644 index 00000000..73b491fe --- /dev/null +++ b/lib/rules/no-url-domains.js @@ -0,0 +1,33 @@ +'use strict'; + +var helpers = require('../helpers'), + url = require('url'); + +module.exports = { + 'name': 'no-url-domains', + 'defaults': {}, + 'detect': function (ast, parser) { + var result = []; + + ast.traverseByType('uri', function (uri) { + uri.traverse(function (item) { + if (item.is('string')) { + var stripped = helpers.stripQuotes(item.content), + parsedUrl = url.parse(stripped, false, true); + + if (parsedUrl.host && parsedUrl.protocol !== 'data:') { + result = helpers.addUnique(result, { + 'ruleId': parser.rule.name, + 'severity': parser.severity, + 'line': item.end.line, + 'column': item.end.column, + 'message': 'Domains in URLs are disallowed' + }); + } + } + }); + }); + + return result; + } +}; diff --git a/lib/rules/no-url-protocols.js b/lib/rules/no-url-protocols.js index 389b4e7a..90080479 100644 --- a/lib/rules/no-url-protocols.js +++ b/lib/rules/no-url-protocols.js @@ -2,30 +2,34 @@ var helpers = require('../helpers'); -var isUrlRegex = /^(https?:)?\/\//; - -var stripQuotes = function (str) { - return str.substring(1, str.length - 1); -}; +var isUrlRegex = /^(https?:)?\/\//, + protocolRelativeRegex = /^(https?:)\/\//; module.exports = { 'name': 'no-url-protocols', - 'defaults': {}, + 'defaults': { + 'allow-protocol-relative-urls': false + }, 'detect': function (ast, parser) { var result = []; ast.traverseByType('uri', function (uri) { uri.traverse(function (item) { if (item.is('string')) { - var stripped = stripQuotes(item.content); + var stripped = helpers.stripQuotes(item.content), + regexSelector = !parser.options['allow-protocol-relative-urls'] ? + isUrlRegex : protocolRelativeRegex, + message = !parser.options['allow-protocol-relative-urls'] ? + 'Protocols and domains in URLs are disallowed' : + 'Protocols in URLS are disallowed'; - if (stripped.match(isUrlRegex)) { + if (stripped.match(regexSelector)) { result = helpers.addUnique(result, { 'ruleId': parser.rule.name, 'severity': parser.severity, 'line': item.end.line, 'column': item.end.column, - 'message': 'Protocols and domains in URLs are disallowed' + 'message': message }); } } diff --git a/lib/rules/shorthand-values.js b/lib/rules/shorthand-values.js index 232ae45b..3df76a4a 100644 --- a/lib/rules/shorthand-values.js +++ b/lib/rules/shorthand-values.js @@ -93,7 +93,13 @@ var scanValue = function (node) { fullVal += '#' + val.content + ''; } - else if (val.is('operator') || val.is('ident') || val.is('number') || val.is('unaryOperator')) { + else if ( + val.is('operator') || + val.is('ident') || + val.is('number') || + val.is('unaryOperator') || + val.is('string') + ) { fullVal += val.content; } diff --git a/lib/rules/space-around-operator.js b/lib/rules/space-around-operator.js index 8a4bc9c7..eb3d6db3 100644 --- a/lib/rules/space-around-operator.js +++ b/lib/rules/space-around-operator.js @@ -262,7 +262,7 @@ var checkSpacing = function (node, i, parent, parser, result) { } } } - return true; + return result; }; module.exports = { diff --git a/lib/rules/space-before-colon.js b/lib/rules/space-before-colon.js index 0d82b468..f61791fa 100644 --- a/lib/rules/space-before-colon.js +++ b/lib/rules/space-before-colon.js @@ -14,7 +14,7 @@ module.exports = { if (delimiter.content === ':') { var previous = parent.content[i - 1]; - if (previous.is('space')) { + if (previous && previous.is('space')) { if (!parser.options.include) { result = helpers.addUnique(result, { 'ruleId': parser.rule.name, diff --git a/lib/rules/variable-name-format.js b/lib/rules/variable-name-format.js index 71e17238..2ea0577d 100644 --- a/lib/rules/variable-name-format.js +++ b/lib/rules/variable-name-format.js @@ -18,6 +18,7 @@ module.exports = { violationMessage = false, name = variable.first().content; + strippedName = name; if (parser.options['allow-leading-underscore'] && name[0] === '_') { diff --git a/lib/rules/zero-unit.js b/lib/rules/zero-unit.js index 3505d35f..ac192960 100644 --- a/lib/rules/zero-unit.js +++ b/lib/rules/zero-unit.js @@ -2,8 +2,22 @@ var helpers = require('../helpers'); -var units = ['em', 'ex', 'ch', 'rem', 'vh', 'vw', 'vmin', 'vmax', - 'px', 'mm', 'cm', 'in', 'pt', 'pc']; +var units = [ + 'em', + 'ex', + 'ch', + 'rem', + 'vh', + 'vw', + 'vmin', + 'vmax', + 'px', + 'mm', + 'cm', + 'in', + 'pt', + 'pc' +]; module.exports = { 'name': 'zero-unit', diff --git a/package.json b/package.json index 61f91546..131bdd60 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sass-lint", - "version": "1.9.1", + "version": "1.10.0", "description": "All Node Sass linter!", "main": "index.js", "scripts": { @@ -29,11 +29,12 @@ "homepage": "https://github.com/sasstools/sass-lint", "dependencies": { "commander": "^2.8.1", - "eslint": "^2.7.0", - "fs-extra": "^0.30.0", + "eslint": "^3.9.1", + "front-matter": "2.1.0", + "fs-extra": "^1.0.0", "glob": "^7.0.0", "globule": "^1.0.0", - "gonzales-pe": "3.4.4", + "gonzales-pe": "3.4.7", "js-yaml": "^3.5.4", "lodash.capitalize": "^4.1.0", "lodash.kebabcase": "^4.0.0", diff --git a/tests/bom-utf8/starts-with-mixin-utf8-bom.scss b/tests/bom-utf8/starts-with-mixin-utf8-bom.scss new file mode 100644 index 00000000..4f12e183 --- /dev/null +++ b/tests/bom-utf8/starts-with-mixin-utf8-bom.scss @@ -0,0 +1,5 @@ +@mixin foo( + $param: 'def' +) { + color: red; +} diff --git a/tests/bom-utf8/var-utf8-bom.scss b/tests/bom-utf8/var-utf8-bom.scss new file mode 100644 index 00000000..bc5580da --- /dev/null +++ b/tests/bom-utf8/var-utf8-bom.scss @@ -0,0 +1,5 @@ +$my-color: red; + +body { + color: $my-color; +} \ No newline at end of file diff --git a/tests/cli.js b/tests/cli.js index 4cc618c3..df7c7fef 100644 --- a/tests/cli.js +++ b/tests/cli.js @@ -6,7 +6,7 @@ var assert = require('assert'), describe('cli', function () { it('should return help instructions', function (done) { - var command = 'sass-lint -h'; + var command = 'node bin/sass-lint -h'; exec(command, function (err, stdout) { if (err) { @@ -20,7 +20,7 @@ describe('cli', function () { }); it('should return a version', function (done) { - var command = 'sass-lint --version'; + var command = 'node bin/sass-lint --version'; exec(command, function (err, stdout) { if (err) { @@ -34,7 +34,7 @@ describe('cli', function () { }); it('should not try to read and lint a directory', function (done) { - var command = 'sass-lint "tests/dir-test/**/*.scss" --no-exit --verbose --format json'; + var command = 'node bin/sass-lint "tests/dir-test/**/*.scss" --no-exit --verbose --format json'; exec(command, function (err, stdout) { var result = JSON.parse(stdout); @@ -52,7 +52,7 @@ describe('cli', function () { }); it('Should accept multiple input paths', function (done) { - var command = 'sass-lint "tests/cli/cli-error.scss, tests/cli/cli-error.sass" --no-exit --verbose'; + var command = 'node bin/sass-lint "tests/cli/cli-error.scss, tests/cli/cli-error.sass" --no-exit --verbose'; exec(command, function (err, stdout) { @@ -68,7 +68,7 @@ describe('cli', function () { }); it('Should accept multiple input globs', function (done) { - var command = 'sass-lint "tests/cli/*.scss, tests/cli/*.sass" --no-exit --verbose'; + var command = 'node bin/sass-lint "tests/cli/*.scss, tests/cli/*.sass" --no-exit --verbose'; exec(command, function (err, stdout) { @@ -84,7 +84,7 @@ describe('cli', function () { }); it('Should accept multiple input paths from a config file', function (done) { - var command = 'sass-lint -c tests/yml/.multiple-inputs.yml --no-exit --verbose'; + var command = 'node bin/sass-lint -c tests/yml/.multiple-inputs.yml --no-exit --verbose'; exec(command, function (err, stdout) { @@ -100,7 +100,7 @@ describe('cli', function () { }); it('Should accept multiple input paths and multiple ignore paths', function (done) { - var command = 'sass-lint "tests/cli/cli-error.scss, tests/cli/cli-error.sass" -i "tests/cli/cli-error.scss, tests/cli/cli-error.sass" --no-exit --verbose'; + var command = 'node bin/sass-lint "tests/cli/cli-error.scss, tests/cli/cli-error.sass" -i "tests/cli/cli-error.scss, tests/cli/cli-error.sass" --no-exit --verbose'; exec(command, function (err, stdout) { @@ -116,7 +116,7 @@ describe('cli', function () { }); it('Should accept multiple input paths and multiple ignores from a config file', function (done) { - var command = 'sass-lint -c tests/yml/.multiple-ignores.yml --no-exit --verbose'; + var command = 'node bin/sass-lint -c tests/yml/.multiple-ignores.yml --no-exit --verbose'; exec(command, function (err, stdout) { @@ -131,7 +131,7 @@ describe('cli', function () { }); it('CLI format option should output JSON', function (done) { - var command = 'sass-lint -c tests/yml/.stylish-output.yml tests/cli/cli.scss --verbose --format json'; + var command = 'node bin/sass-lint -c tests/yml/.stylish-output.yml tests/cli/cli.scss --verbose --format json'; exec(command, function (err, stdout) { @@ -151,7 +151,7 @@ describe('cli', function () { }); it('CLI output option should write to test file', function (done) { - var command = 'sass-lint -c tests/yml/.stylish-output.yml tests/cli/cli.scss --verbose --format json --output tests/cli-output.json', + var command = 'node bin/sass-lint -c tests/yml/.stylish-output.yml tests/cli/cli.scss --verbose --format json --output tests/cli-output.json', outputFile = path.resolve(process.cwd(), 'tests/cli-output.json'); exec(command, function (err) { @@ -174,7 +174,7 @@ describe('cli', function () { }); it('CLI output option should write JSON to test file', function (done) { - var command = 'sass-lint -c tests/yml/.stylish-output.yml tests/cli/cli.scss --verbose --format json --output tests/cli-output.json', + var command = 'node bin/sass-lint -c tests/yml/.stylish-output.yml tests/cli/cli.scss --verbose --format json --output tests/cli-output.json', outputFile = path.resolve(process.cwd(), 'tests/cli-output.json'); exec(command, function (err) { @@ -207,7 +207,7 @@ describe('cli', function () { }); it('CLI output option should write JSON to test file when upper case format is used', function (done) { - var command = 'sass-lint -c tests/yml/.stylish-output.yml tests/cli/cli.scss --verbose --format JSON --output tests/cli-output.json', + var command = 'node bin/sass-lint -c tests/yml/.stylish-output.yml tests/cli/cli.scss --verbose --format JSON --output tests/cli-output.json', outputFile = path.resolve(process.cwd(), 'tests/cli-output.json'); exec(command, function (err) { @@ -242,7 +242,7 @@ describe('cli', function () { // Test custom config path it('should return JSON from a custom config', function (done) { - var command = 'sass-lint -c tests/yml/.color-keyword-errors.yml tests/cli/cli.scss --verbose'; + var command = 'node bin/sass-lint -c tests/yml/.color-keyword-errors.yml tests/cli/cli.scss --verbose'; exec(command, function (err, stdout) { @@ -264,7 +264,7 @@ describe('cli', function () { // Test 0 errors/warnings when rules set to 0 in config it('output should return no errors/warnings', function (done) { - var command = 'sass-lint -c tests/yml/.json-lint.yml tests/cli/cli.scss --verbose'; + var command = 'node bin/sass-lint -c tests/yml/.json-lint.yml tests/cli/cli.scss --verbose'; exec(command, function (err, stdout) { @@ -286,7 +286,7 @@ describe('cli', function () { // Test 1 warning when rules set to 0 in config it('should return a warning', function (done) { - var command = 'sass-lint -c tests/yml/.color-keyword-errors.yml tests/cli/cli.scss --verbose'; + var command = 'node bin/sass-lint -c tests/yml/.color-keyword-errors.yml tests/cli/cli.scss --verbose'; exec(command, function (err, stdout) { @@ -315,7 +315,7 @@ describe('cli', function () { }); it('should return a warning - stylish', function (done) { - var command = 'sass-lint -c tests/yml/.stylish-errors.yml tests/cli/cli.scss --verbose', + var command = 'node bin/sass-lint -c tests/yml/.stylish-errors.yml tests/cli/cli.scss --verbose', expectedOutputLength = 154; exec(command, function (err, stdout) { @@ -332,7 +332,7 @@ describe('cli', function () { }); it('should not include ignored paths', function (done) { - var command = 'sass-lint -i "**/*.scss" -v -q --format json "**/cli/*.scss"'; + var command = 'node bin/sass-lint -i "**/*.scss" -v -q --format json "**/cli/*.scss"'; exec(command, function (err, stdout) { @@ -347,7 +347,7 @@ describe('cli', function () { }); it('should not include multiple ignored paths', function (done) { - var command = 'sass-lint -i "**/*.scss, **/*.sass" -q -v --format json'; + var command = 'node bin/sass-lint -i "**/*.scss, **/*.sass" -q -v --format json'; exec(command, function (err, stdout) { @@ -363,7 +363,7 @@ describe('cli', function () { }); it('should override filename convention if a valid --syntax is provided', function (done) { - var command = 'sass-lint --syntax scss tests/cli/cli.txt --verbose --format json'; + var command = 'node bin/sass-lint --syntax scss tests/cli/cli.txt --verbose --format json'; exec(command, function (err, stdout) { @@ -383,32 +383,32 @@ describe('cli', function () { }); - it('should exit with exit code 1 when quiet', function (done) { - var command = 'sass-lint -c tests/yml/.error-output.yml tests/cli/cli-error.scss --verbose --no-exit'; + it('should exit with error when quiet', function (done) { + var command = 'node bin/sass-lint -c tests/yml/.error-output.yml tests/cli/cli-error.scss --verbose --no-exit'; exec(command, function (err) { - if (err.code === 1) { + if (err) { return done(); } - return done(new Error('Error code not 1')); + return done(new Error('No error on exit')); }); }); - it('should exit with exit code 1 when more warnings than --max-warnings', function (done) { - var command = 'sass-lint -c tests/yml/.color-keyword-errors.yml tests/cli/cli.scss --max-warnings 0'; + it('should exit with error when more warnings than --max-warnings', function (done) { + var command = 'node bin/sass-lint -c tests/yml/.color-keyword-errors.yml tests/cli/cli.scss --max-warnings 0'; exec(command, function (err) { - if (err && err.code === 1) { + if (err) { return done(); } - return done(new Error('Error code not 1')); + return done(new Error('No error on exit')); }); }); it('should not exit with an error if no config is specified', function (done) { - var command = 'sass-lint tests/cli/cli-clean.scss --verbose --no-exit'; + var command = 'node bin/sass-lint tests/cli/cli-clean.scss --verbose --no-exit'; exec(command, function (err) { if (!err) { @@ -423,7 +423,7 @@ describe('cli', function () { * We disabled eslints handle callback err rule here as we are deliberately throwing errors that we don't care about */ it('parse errors should report as a lint error', function (done) { - var command = 'sass-lint --config tests/yml/.stylish-output.yml tests/sass/parse.scss --verbose --no-exit --format json'; + var command = 'node bin/sass-lint --config tests/yml/.stylish-output.yml tests/sass/parse.scss --verbose --no-exit --format json'; exec(command, function (err, stdout) { // eslint-disable-line handle-callback-err var result = JSON.parse(stdout)[0]; @@ -434,7 +434,7 @@ describe('cli', function () { }); it('parse errors should report as severity 2', function (done) { - var command = 'sass-lint --config tests/yml/.stylish-output.yml tests/sass/parse.scss --verbose --no-exit --format json'; + var command = 'node bin/sass-lint --config tests/yml/.stylish-output.yml tests/sass/parse.scss --verbose --no-exit --format json'; exec(command, function (err, stdout) { // eslint-disable-line handle-callback-err var result = JSON.parse(stdout)[0], @@ -447,7 +447,7 @@ describe('cli', function () { }); it('parse errors should report the correct message', function (done) { - var command = 'sass-lint --config tests/yml/.stylish-output.yml tests/sass/parse.scss --verbose --no-exit --format json'; + var command = 'node bin/sass-lint --config tests/yml/.stylish-output.yml tests/sass/parse.scss --verbose --no-exit --format json'; exec(command, function (err, stdout) { // eslint-disable-line handle-callback-err var result = JSON.parse(stdout)[0], @@ -460,7 +460,7 @@ describe('cli', function () { }); it('parse errors rule Id should be \'Fatal\'', function (done) { - var command = 'sass-lint --config tests/yml/.stylish-output.yml tests/sass/parse.scss --verbose --no-exit --format json'; + var command = 'node bin/sass-lint --config tests/yml/.stylish-output.yml tests/sass/parse.scss --verbose --no-exit --format json'; exec(command, function (err, stdout) { // eslint-disable-line handle-callback-err var result = JSON.parse(stdout)[0], @@ -472,3 +472,43 @@ describe('cli', function () { }); }); }); + +// ============================================================================== +// UTF-8 BOM +// ============================================================================== + +describe('Reading files with UTF-8 BOM', function () { + before(function () { + var testText = fs.readFileSync('tests/bom-utf8/starts-with-mixin-utf8-bom.scss').toString(); + if (testText.charCodeAt(0) !== 0xFEFF) { + throw new Error('BOM test files have no BOM markers'); + } + }); + + it('should not throw a parse error from file containing a BOM', function (done) { + var command = 'node bin/sass-lint -v tests/bom-utf8/starts-with-mixin-utf8-bom.scss --format json'; + + exec(command, function (err, stdout) { // eslint-disable-line handle-callback-err + var result = JSON.parse(stdout)[0]; + + // Files with BOM markers that start with a mixin throw a fatal error + // https://github.com/sasstools/sass-lint/issues/880 + assert.equal(result.errorCount, 0); + done(); + }); + }); + + it('should return the correct amount of warnings from a file containing BOM markers', function (done) { + var command = 'node bin/sass-lint -v tests/bom-utf8/var-utf8-bom.scss --format json'; + + exec(command, function (err, stdout) { // eslint-disable-line handle-callback-err + var result = JSON.parse(stdout)[0]; + + // Files starting with variables threw extra errors + // see https://github.com/sasstools/sass-lint/issues/880 + assert.equal(result.errorCount, 0); + assert.equal(result.warningCount, 2); + done(); + }); + }); +}); diff --git a/tests/failures.js b/tests/failures.js new file mode 100644 index 00000000..3127d451 --- /dev/null +++ b/tests/failures.js @@ -0,0 +1,90 @@ +'use strict'; + +var lint = require('../index'), + assert = require('assert'), + exceptions = require('../lib/exceptions'); + +describe('failures', function () { + it('should raise SassLintFailureError if indentation is set to error', function (done) { + assert.throws( + function () { + var results = lint.lintFiles('tests/sass/indentation/indentation-spaces.scss', {rules: {indentation: 2}}); // 14 errors + lint.failOnError(results); // Set indentation to error + }, + exceptions.SassLintFailureError + ); + assert.throws( + function () { + var results = lint.lintFiles('tests/sass/indentation/indentation-spaces.scss', {}, 'tests/yml/.indentation-error.yml'); // 14 errors + lint.failOnError(results); // Set indentation to error + }, + exceptions.SassLintFailureError + ); + + done(); + }); + + it('should not raise error if indentation is only set to warn', function (done) { + // These should produce 55 warnings and 0 errors + var directResults = lint.lintFiles('sass/indentation/indentation-spaces.scss', {rules: {indentation: 1}}); + var configResults = lint.lintFiles('sass/indentation/indentation-spaces.scss', {}, 'yml/.indentation-warn.yml'); + lint.failOnError(directResults); + lint.failOnError(configResults); + + done(); + }); + + it('should raise MaxWarningsExceededError if warnings exceed `max-warnings` setting', function (done) { + assert.throws( + function () { + var results = lint.lintFiles('tests/sass/indentation/indentation-spaces.scss', {}); // 55 warnings + lint.failOnError(results, {options: {'max-warnings': 10}}); + }, + exceptions.MaxWarningsExceededError + ); + assert.throws( + function () { + var results = lint.lintFiles('tests/sass/indentation/indentation-spaces.scss', {}); // 55 warnings + lint.failOnError(results, {}, 'tests/yml/.max-10-warnings.yml'); + }, + exceptions.MaxWarningsExceededError + ); + + done(); + }); + + it('should raise MaxWarningsExceededError if warnings exceed `max-warnings` of zero', function (done) { + assert.throws( + function () { + var results = lint.lintFiles('tests/sass/indentation/indentation-spaces.scss', {}); // 55 warnings + lint.failOnError(results, {options: {'max-warnings': 0}}); + }, + exceptions.MaxWarningsExceededError + ); + assert.throws( + function () { + var results = lint.lintFiles('tests/sass/indentation/indentation-spaces.scss', {}); // 55 warnings + lint.failOnError(results, {}, 'tests/yml/.max-0-warnings.yml'); + }, + exceptions.MaxWarningsExceededError + ); + + done(); + }); + + it('should not raise error if warnings do not exceed `max-warnings` setting', function (done) { + var results = lint.lintFiles('sass/indentation/indentation-spaces.scss', {}); // 55 warnings + lint.failOnError(results, {'max-warnings': 100}); // should succceed + lint.failOnError(results, {}, 'yml/.max-100-warnings.yml'); // should succeed + + done(); + }); + + it('should not raise error if no warnings even if `max-warnings` is zero', function (done) { + var results = lint.lintFiles('sass/success.scss', {}); // no warnings + lint.failOnError(results, {'max-warnings': 0}); // should still succceed + lint.failOnError(results, {}, 'yml/.max-0-warnings.yml'); // should still succeed + + done(); + }); +}); diff --git a/tests/helpers/isStrictBEM.js b/tests/helpers/isStrictBEM.js index 6c474519..0fa85d39 100644 --- a/tests/helpers/isStrictBEM.js +++ b/tests/helpers/isStrictBEM.js @@ -89,11 +89,11 @@ describe('helpers - isStrictBEM', function () { done(); }); - it('isStrictBEM - [\'abc_def\' - false]', function (done) { + it('isStrictBEM - [\'abc_def\' - true]', function (done) { var result = helpers.isStrictBEM('abc_def'); - assert.equal(false, result); + assert.equal(true, result); done(); }); diff --git a/tests/helpers/stripBom.js b/tests/helpers/stripBom.js new file mode 100644 index 00000000..523e0266 --- /dev/null +++ b/tests/helpers/stripBom.js @@ -0,0 +1,28 @@ +'use strict'; + +var assert = require('assert'), + helpers = require('../../lib/helpers'), + fs = require('fs'); + +describe('helpers - stripBom', function () { + + ////////////////////////////// + // Strip BOM + ////////////////////////////// + + it('should remove the BOM marker', function (done) { + var file = fs.readFileSync('tests/bom-utf8/starts-with-mixin-utf8-bom.scss').toString(); + assert.equal(file.charCodeAt(0), 0xFEFF); + assert.notEqual(helpers.stripBom(file).charCodeAt(0), 0xFEFF); + done(); + }); + + it('should throw an error if not passed a string', function (done) { + assert.throws( + function () { + helpers.stripBom(8); + }, Error + ); + done(); + }); +}); diff --git a/tests/main.js b/tests/main.js index 9c54feb2..28131df9 100644 --- a/tests/main.js +++ b/tests/main.js @@ -140,21 +140,35 @@ describe('sass lint', function () { // ============================================================================== it('should not try to blindly read and lint a directory', function (done) { var fileSpy = sinon.spy(lint, 'lintText'); - lintFiles('tests/dir-test/**/*.scss', {options: { - 'merge-default-rules': false - }, - rules: { - 'no-ids': 1 - }}, '', function (data) { - assert.equal(1, data[0].warningCount); - assert.equal(0, data[0].errorCount); - assert.equal(1, data[0].messages.length); - - assert(fileSpy.called); - assert(fileSpy.calledOnce); - assert(fileSpy.calledWithMatch({format: 'scss', filename: 'tests/dir-test/dir.scss/test.scss'})); - assert(fileSpy.neverCalledWithMatch({filename: 'tests/dir-test/dir.scss'})); - fileSpy.reset(); + lintFiles('tests/dir-test/**/*.scss', { + options: { + 'merge-default-rules': false + }, + rules: { + 'no-ids': 1 + }}, '', function (data) { + assert.equal(1, data[0].warningCount); + assert.equal(0, data[0].errorCount); + assert.equal(1, data[0].messages.length); + + assert(fileSpy.called); + assert(fileSpy.calledOnce); + assert(fileSpy.calledWithMatch({format: 'scss', filename: 'tests/dir-test/dir.scss/test.scss'})); + assert(fileSpy.neverCalledWithMatch({filename: 'tests/dir-test/dir.scss'})); + fileSpy.reset(); + done(); + } + ); + }); + + // ============================================================================== + // Parse files with YAML front matter + // ============================================================================== + + it('should parse a file with front matter correctly and without parse error', function (done) { + lintFile('front-matter/front-matter.scss', function (data) { + assert.equal(0, data.errorCount); + assert.equal(2, data.warningCount); done(); }); }); diff --git a/tests/ruleToggler.js b/tests/ruleToggler.js new file mode 100644 index 00000000..22b3c600 --- /dev/null +++ b/tests/ruleToggler.js @@ -0,0 +1,326 @@ +var path = require('path'), + fs = require('fs'), + groot = require('../lib/groot'), + ruleToggler = require('../lib/ruleToggler'), + assert = require('assert'), + deepEqual = require('deep-equal'); + +var getToggledRules = ruleToggler.getToggledRules, + isResultEnabled = ruleToggler.isResultEnabled; + +var generateToggledRules = function (filename) { + var filePath = path.join(process.cwd(), 'tests', 'sass', filename); + var file = { + 'text': fs.readFileSync(filePath), + 'format': path.extname(filePath).replace('.', ''), + 'filename': path.basename(filePath) + }; + var ast = groot(file.text, file.format, file.filename); + return getToggledRules(ast); +}; + +describe('rule toggling', function () { + describe('getToggledRules', function () { + ////////////////////////////// + // SCSS syntax tests + ////////////////////////////// + it('should allow all rules to be disabled', function () { + assert(deepEqual(generateToggledRules('ruleToggler-disable-all.scss'), { + globalEnable: [[false, 1, 1]], + ruleEnable: {} + }) === true); + }); + it('should allow all rules to be disabled then re-enabled', function () { + var ruleToggles = generateToggledRules('ruleToggler-disable-all-then-reenable.scss'); + assert(deepEqual(ruleToggles, { + globalEnable: [ + [false, 1, 1], + [true, 3, 1] + ], + ruleEnable: {} + }) === true); + }); + it('should allow a single rule to be disabled', function () { + assert(deepEqual(generateToggledRules('ruleToggler-disable-a-rule.scss'), { + globalEnable: [], + ruleEnable: { + a: [[false, 1, 1]] + } + }) === true); + }); + it('should allow multiple rules to be disabled', function () { + assert(deepEqual(generateToggledRules('ruleToggler-disable-multiple-rules.scss'), { + globalEnable: [], + ruleEnable: { + a: [[false, 1, 1]], + b: [[false, 1, 1]], + c: [[false, 1, 1]], + d: [[false, 1, 1]] + } + }) === true); + }); + it('should be able to disable a single line', function () { + var ruleToggles = generateToggledRules('ruleToggler-disable-a-line.scss'); + assert(deepEqual(ruleToggles, { + globalEnable: [], + ruleEnable: { + a: [[false, 2, 1], [true, 3, 1]] + } + }) === true); + }); + it('should be able to disable a block of code', function () { + var ruleToggles = generateToggledRules('ruleToggler-disable-a-block.scss'); + assert(deepEqual(ruleToggles, { + globalEnable: [], + ruleEnable: { + a: [[false, 1, 3], [true, 3, 1]] + } + }) === true); + }); + it('should be able to enable a disabled rule', function () { + var ruleToggles = generateToggledRules('ruleToggler-disable-then-enable.scss'); + assert(deepEqual(ruleToggles, { + globalEnable: [], + ruleEnable: { + a: [[false, 2, 5], [true, 4, 5]] + } + }) === true); + }); + it('should ignore comments that don\'t fit known formats', function () { + var ruleToggles = generateToggledRules('ruleToggler-ignore-unknown.scss'); + assert(deepEqual(ruleToggles, { + globalEnable: [], + ruleEnable: {} + }) === true); + }); + it('should ignore empty files', function () { + var ruleToggles = generateToggledRules('empty-file.scss'); + assert(deepEqual(ruleToggles, { + globalEnable: [], + ruleEnable: {} + }) === true); + }); + it('should ignore empty comments', function () { + var ruleToggles = generateToggledRules('ruleToggler-empty-comment.scss'); + assert(deepEqual(ruleToggles, { + globalEnable: [], + ruleEnable: {} + }) === true); + }); + it('should be ordered', function () { + var ruleToggles = generateToggledRules('ruleToggler-guarantee-order.scss'); + assert(deepEqual(ruleToggles, { + globalEnable: [], + ruleEnable: { + a: [[false, 1, 3], + [false, 2, 5], + [true, 6, 1], + [false, 8, 3], + [false, 8, 5], + [true, 12, 1], + [false, 14, 6], + [false, 14, 32]] + } + }) === true); + }); + + ////////////////////////////// + // SASS syntax tests + ////////////////////////////// + it('should allow all rules to be disabled', function () { + assert(deepEqual(generateToggledRules('ruleToggler-disable-all.sass'), { + globalEnable: [[false, 1, 1]], + ruleEnable: {} + }) === true); + }); + it('should allow all rules to be disabled then re-enabled', function () { + var ruleToggles = generateToggledRules('ruleToggler-disable-all-then-reenable.sass'); + assert(deepEqual(ruleToggles, { + globalEnable: [ + [false, 1, 1], + [true, 3, 1] + ], + ruleEnable: {} + }) === true); + }); + it('should allow a single rule to be disabled', function () { + assert(deepEqual(generateToggledRules('ruleToggler-disable-a-rule.sass'), { + globalEnable: [], + ruleEnable: { + a: [[false, 1, 1]] + } + }) === true); + }); + it('should allow multiple rules to be disabled', function () { + assert(deepEqual(generateToggledRules('ruleToggler-disable-multiple-rules.sass'), { + globalEnable: [], + ruleEnable: { + a: [[false, 1, 1]], + b: [[false, 1, 1]], + c: [[false, 1, 1]], + d: [[false, 1, 1]] + } + }) === true); + }); + it('should be able to disable a single line', function () { + var ruleToggles = generateToggledRules('ruleToggler-disable-a-line.sass'); + assert(deepEqual(ruleToggles, { + globalEnable: [], + ruleEnable: { + a: [[false, 2, 1], [true, 3, 1]] + } + }) === true); + }); + it('should be able to disable a block of code', function () { + var ruleToggles = generateToggledRules('ruleToggler-disable-a-block.sass'); + assert(deepEqual(ruleToggles, { + globalEnable: [], + ruleEnable: { + a: [[false, 2, 1], [true, 2, 32]] + } + }) === true); + }); + it('should be able to enable a disabled rule', function () { + var ruleToggles = generateToggledRules('ruleToggler-disable-then-enable.sass'); + assert(deepEqual(ruleToggles, { + globalEnable: [], + ruleEnable: { + a: [[false, 2, 5], [true, 4, 5]] + } + }) === true); + }); + it('should ignore comments that don\'t fit known formats', function () { + var ruleToggles = generateToggledRules('ruleToggler-ignore-unknown.sass'); + assert(deepEqual(ruleToggles, { + globalEnable: [], + ruleEnable: {} + }) === true); + }); + it('should ignore empty files', function () { + var ruleToggles = generateToggledRules('empty-file.sass'); + assert(deepEqual(ruleToggles, { + globalEnable: [], + ruleEnable: {} + }) === true); + }); + it('should ignore empty comments', function () { + var ruleToggles = generateToggledRules('ruleToggler-empty-comment.sass'); + assert(deepEqual(ruleToggles, { + globalEnable: [], + ruleEnable: {} + }) === true); + }); + it('should be ordered', function () { + var ruleToggles = generateToggledRules('ruleToggler-guarantee-order.sass'); + assert(deepEqual(ruleToggles, { + globalEnable: [], + ruleEnable: { + a: [[false, 2, 1], + [false, 2, 5], + [true, 5, 20], + [false, 8, 1], + [false, 8, 5], + [true, 11, 20], + [false, 14, 5], + [false, 15, 5]] + } + }) === true); + }); + }); + describe('isResultEnabled', function () { + it('should disable all rules if global is disabled', function () { + assert(isResultEnabled({ + globalEnable: [[false, 1, 1]], + ruleEnable: {} + })({ + ruleId: 'anything', + line: 2, + column: 1 + }) === false); + }); + it('should disable a rule', function () { + assert(isResultEnabled({ + globalEnable: [], + ruleEnable: { + a: [[false, 1, 1]] + } + })({ + ruleId: 'a', + line: 2, + column: 1 + }) === false); + }); + it('should not disable an unrelated rule', function () { + assert(isResultEnabled({ + globalEnable: [], + ruleEnable: { + b: [[false, 1, 1]] + } + })({ + ruleId: 'a', + line: 2, + column: 1 + }) === true); + }); + it('should support enabling a previously disabled rule', function () { + assert(isResultEnabled({ + globalEnable: [], + ruleEnable: { + a: [[false, 1, 1], [true, 2, 1]] + } + })({ + ruleId: 'a', + line: 3, + column: 1 + }) === true); + }); + it('should support disabling a previously re-enabled rule', function () { + assert(isResultEnabled({ + globalEnable: [], + ruleEnable: { + a: [[false, 1, 1], [true, 2, 1], [false, 3, 1]] + } + })({ + ruleId: 'a', + line: 4, + column: 1 + }) === false); + }); + it('should support enabling a previously re-enabled then disabled rule (in enabled part)', function () { + assert(isResultEnabled({ + globalEnable: [], + ruleEnable: { + a: [[false, 1, 1], [true, 2, 1], [false, 3, 1], [true, 4, 1]] + } + })({ + ruleId: 'a', + line: 5, + column: 1 + }) === true); + }); + it('should support enabling a previously re-enabled then disabled rule (in disabled part)', function () { + assert(isResultEnabled({ + globalEnable: [], + ruleEnable: { + a: [[false, 1, 1], [true, 2, 1], [false, 3, 1], [true, 4, 1]] + } + })({ + ruleId: 'a', + line: 3, + column: 10 + }) === false); + }); + it('should support disabling a rule that is later re-enabled', function () { + assert(isResultEnabled({ + globalEnable: [], + ruleEnable: { + a: [[false, 1, 1], [true, 3, 1], [false, 4, 1]] + } + })({ + ruleId: 'a', + line: 2, + column: 1 + }) === false); + }); + }); +}); diff --git a/tests/rules/border-zero.js b/tests/rules/border-zero.js index da2c280a..1c026aa6 100644 --- a/tests/rules/border-zero.js +++ b/tests/rules/border-zero.js @@ -9,6 +9,20 @@ describe('border zero - scss', function () { var file = lint.file('border-zero.scss'); it('[convention: 0]', function (done) { + lint.test(file, { + 'border-zero': [ + 1, + { + 'convention': 0 + } + ] + }, function (data) { + lint.assert.equal(3, data.warningCount); + done(); + }); + }); + + it('[convention: \'0\']', function (done) { lint.test(file, { 'border-zero': 1 }, function (data) { @@ -30,6 +44,21 @@ describe('border zero - scss', function () { done(); }); }); + + it('invalid convention [convention: \'zero\']', function (done) { + // defaults to convention 0 + lint.test(file, { + 'border-zero': [ + 1, + { + 'convention': 'zero' + } + ] + }, function (data) { + lint.assert.equal(4, data.warningCount); + done(); + }); + }); }); ////////////////////////////// @@ -39,6 +68,20 @@ describe('border zero - sass', function () { var file = lint.file('border-zero.sass'); it('[convention: 0]', function (done) { + lint.test(file, { + 'border-zero': [ + 1, + { + 'convention': 0 + } + ] + }, function (data) { + lint.assert.equal(3, data.warningCount); + done(); + }); + }); + + it('[convention: \'0\']', function (done) { lint.test(file, { 'border-zero': 1 }, function (data) { @@ -60,4 +103,19 @@ describe('border zero - sass', function () { done(); }); }); + + it('invalid convention [convention: \'zero\']', function (done) { + // defaults to convention 0 + lint.test(file, { + 'border-zero': [ + 1, + { + 'convention': 'zero' + } + ] + }, function (data) { + lint.assert.equal(4, data.warningCount); + done(); + }); + }); }); diff --git a/tests/rules/class-name-format.js b/tests/rules/class-name-format.js index 220f382c..c5f5365c 100644 --- a/tests/rules/class-name-format.js +++ b/tests/rules/class-name-format.js @@ -12,7 +12,7 @@ describe('class name format - scss', function () { lint.test(file, { 'class-name-format': 1 }, function (data) { - lint.assert.equal(27, data.warningCount); + lint.assert.equal(31, data.warningCount); done(); }); }); @@ -26,7 +26,7 @@ describe('class name format - scss', function () { } ] }, function (data) { - lint.assert.equal(26, data.warningCount); + lint.assert.equal(30, data.warningCount); done(); }); }); @@ -40,7 +40,7 @@ describe('class name format - scss', function () { } ] }, function (data) { - lint.assert.equal(36, data.warningCount); + lint.assert.equal(40, data.warningCount); done(); }); }); @@ -54,7 +54,7 @@ describe('class name format - scss', function () { } ] }, function (data) { - lint.assert.equal(36, data.warningCount); + lint.assert.equal(40, data.warningCount); done(); }); }); @@ -68,7 +68,7 @@ describe('class name format - scss', function () { } ] }, function (data) { - lint.assert.equal(32, data.warningCount); + lint.assert.equal(36, data.warningCount); done(); }); }); @@ -82,7 +82,7 @@ describe('class name format - scss', function () { } ] }, function (data) { - lint.assert.equal(22, data.warningCount); + lint.assert.equal(17, data.warningCount); done(); }); }); @@ -96,7 +96,7 @@ describe('class name format - scss', function () { } ] }, function (data) { - lint.assert.equal(20, data.warningCount); + lint.assert.equal(24, data.warningCount); done(); }); }); @@ -110,7 +110,7 @@ describe('class name format - scss', function () { } ] }, function (data) { - lint.assert.equal(38, data.warningCount); + lint.assert.equal(42, data.warningCount); done(); }); }); @@ -126,7 +126,7 @@ describe('class name format - scss', function () { } ] }, function (data) { - lint.assert.equal(34, data.warningCount); + lint.assert.equal(38, data.warningCount); lint.assert.equal(data.messages[0].message, message); done(); }); @@ -143,7 +143,7 @@ describe('class name format - sass', function () { lint.test(file, { 'class-name-format': 1 }, function (data) { - lint.assert.equal(27, data.warningCount); + lint.assert.equal(31, data.warningCount); done(); }); }); @@ -157,7 +157,7 @@ describe('class name format - sass', function () { } ] }, function (data) { - lint.assert.equal(26, data.warningCount); + lint.assert.equal(30, data.warningCount); done(); }); }); @@ -171,7 +171,7 @@ describe('class name format - sass', function () { } ] }, function (data) { - lint.assert.equal(36, data.warningCount); + lint.assert.equal(40, data.warningCount); done(); }); }); @@ -185,7 +185,7 @@ describe('class name format - sass', function () { } ] }, function (data) { - lint.assert.equal(36, data.warningCount); + lint.assert.equal(40, data.warningCount); done(); }); }); @@ -199,7 +199,7 @@ describe('class name format - sass', function () { } ] }, function (data) { - lint.assert.equal(32, data.warningCount); + lint.assert.equal(36, data.warningCount); done(); }); }); @@ -213,7 +213,7 @@ describe('class name format - sass', function () { } ] }, function (data) { - lint.assert.equal(22, data.warningCount); + lint.assert.equal(17, data.warningCount); done(); }); }); @@ -227,7 +227,7 @@ describe('class name format - sass', function () { } ] }, function (data) { - lint.assert.equal(20, data.warningCount); + lint.assert.equal(24, data.warningCount); done(); }); }); @@ -241,7 +241,7 @@ describe('class name format - sass', function () { } ] }, function (data) { - lint.assert.equal(38, data.warningCount); + lint.assert.equal(42, data.warningCount); done(); }); }); @@ -257,7 +257,7 @@ describe('class name format - sass', function () { } ] }, function (data) { - lint.assert.equal(34, data.warningCount); + lint.assert.equal(38, data.warningCount); lint.assert.equal(data.messages[0].message, message); done(); }); diff --git a/tests/rules/declarations-before-nesting.js b/tests/rules/declarations-before-nesting.js new file mode 100644 index 00000000..e8f4b59c --- /dev/null +++ b/tests/rules/declarations-before-nesting.js @@ -0,0 +1,35 @@ +'use strict'; + +var lint = require('./_lint'); + +////////////////////////////// +// SCSS syntax tests +////////////////////////////// +describe('declarations before nesting - scss', function () { + var file = lint.file('declarations-before-nesting.scss'); + + it('enforce', function (done) { + lint.test(file, { + 'declarations-before-nesting': 1 + }, function (data) { + lint.assert.equal(4, data.warningCount); + done(); + }); + }); +}); + +////////////////////////////// +// Sass syntax tests +////////////////////////////// +describe('declarations before nesting - sass', function () { + var file = lint.file('declarations-before-nesting.sass'); + + it('enforce', function (done) { + lint.test(file, { + 'declarations-before-nesting': 1 + }, function (data) { + lint.assert.equal(4, data.warningCount); + done(); + }); + }); +}); diff --git a/tests/rules/function-name-format.js b/tests/rules/function-name-format.js index ba2bb02a..beda9f51 100644 --- a/tests/rules/function-name-format.js +++ b/tests/rules/function-name-format.js @@ -65,7 +65,7 @@ describe('function name format - scss', function () { } ] }, function (data) { - lint.assert.equal(13, data.warningCount); + lint.assert.equal(10, data.warningCount); done(); }); }); @@ -177,7 +177,7 @@ describe('function name format - sass', function () { } ] }, function (data) { - lint.assert.equal(13, data.warningCount); + lint.assert.equal(10, data.warningCount); done(); }); }); diff --git a/tests/rules/max-file-line-count.js b/tests/rules/max-file-line-count.js new file mode 100644 index 00000000..89a8690f --- /dev/null +++ b/tests/rules/max-file-line-count.js @@ -0,0 +1,63 @@ +'use strict'; + +var lint = require('./_lint'); + +////////////////////////////// +// SCSS syntax tests +////////////////////////////// +describe('max-file-line-count - scss', function () { + var file = lint.file('max-file-line-count.scss'); + + it('enforce [default]', function (done) { + lint.test(file, { + 'max-file-line-count': 1 + }, function (data) { + lint.assert.equal(1, data.warningCount); + done(); + }); + }); + + it('enforce [length: 3000]', function (done) { + lint.test(file, { + 'max-file-line-count': [ + 1, + { + length: 3000 + } + ] + }, function (data) { + lint.assert.equal(0, data.warningCount); + done(); + }); + }); +}); + +////////////////////////////// +// Sass syntax tests +////////////////////////////// +describe('max-file-line-count - sass', function () { + var file = lint.file('max-file-line-count.sass'); + + it('enforce', function (done) { + lint.test(file, { + 'max-file-line-count': 1 + }, function (data) { + lint.assert.equal(1, data.warningCount); + done(); + }); + }); + + it('enforce [length: 3000]', function (done) { + lint.test(file, { + 'max-file-line-count': [ + 1, + { + length: 3000 + } + ] + }, function (data) { + lint.assert.equal(0, data.warningCount); + done(); + }); + }); +}); diff --git a/tests/rules/max-line-length.js b/tests/rules/max-line-length.js new file mode 100644 index 00000000..0109174e --- /dev/null +++ b/tests/rules/max-line-length.js @@ -0,0 +1,63 @@ +'use strict'; + +var lint = require('./_lint'); + +////////////////////////////// +// SCSS syntax tests +////////////////////////////// +describe('max-line-length - scss', function () { + var file = lint.file('max-line-length.scss'); + + it('enforce [default]', function (done) { + lint.test(file, { + 'max-line-length': 1 + }, function (data) { + lint.assert.equal(5, data.warningCount); + done(); + }); + }); + + it('enforce [length: 79]', function (done) { + lint.test(file, { + 'max-line-length': [ + 1, + { + length: 79 + } + ] + }, function (data) { + lint.assert.equal(8, data.warningCount); + done(); + }); + }); +}); + +////////////////////////////// +// Sass syntax tests +////////////////////////////// +describe('max-line-length - sass', function () { + var file = lint.file('max-line-length.sass'); + + it('enforce', function (done) { + lint.test(file, { + 'max-line-length': 1 + }, function (data) { + lint.assert.equal(5, data.warningCount); + done(); + }); + }); + + it('enforce [length: 79]', function (done) { + lint.test(file, { + 'max-line-length': [ + 1, + { + length: 79 + } + ] + }, function (data) { + lint.assert.equal(8, data.warningCount); + done(); + }); + }); +}); diff --git a/tests/rules/mixin-name-format.js b/tests/rules/mixin-name-format.js index 3b57f45e..4c985802 100644 --- a/tests/rules/mixin-name-format.js +++ b/tests/rules/mixin-name-format.js @@ -65,7 +65,7 @@ describe('mixin name format - scss', function () { } ] }, function (data) { - lint.assert.equal(13, data.warningCount); + lint.assert.equal(9, data.warningCount); done(); }); }); @@ -177,7 +177,7 @@ describe('mixin name format - sass', function () { } ] }, function (data) { - lint.assert.equal(13, data.warningCount); + lint.assert.equal(9, data.warningCount); done(); }); }); diff --git a/tests/rules/no-duplicate-properties.js b/tests/rules/no-duplicate-properties.js index c703d25a..d51bf824 100644 --- a/tests/rules/no-duplicate-properties.js +++ b/tests/rules/no-duplicate-properties.js @@ -12,7 +12,7 @@ describe('no duplicate properties - scss', function () { lint.test(file, { 'no-duplicate-properties': 1 }, function (data) { - lint.assert.equal(6, data.warningCount); + lint.assert.equal(7, data.warningCount); done(); }); }); @@ -28,7 +28,7 @@ describe('no duplicate properties - scss', function () { } ] }, function (data) { - lint.assert.equal(5, data.warningCount); + lint.assert.equal(6, data.warningCount); done(); }); }); @@ -45,7 +45,7 @@ describe('no duplicate properties - scss', function () { } ] }, function (data) { - lint.assert.equal(4, data.warningCount); + lint.assert.equal(5, data.warningCount); done(); }); }); @@ -61,7 +61,7 @@ describe('no duplicate properties - sass', function () { lint.test(file, { 'no-duplicate-properties': 1 }, function (data) { - lint.assert.equal(6, data.warningCount); + lint.assert.equal(7, data.warningCount); done(); }); }); @@ -77,7 +77,7 @@ describe('no duplicate properties - sass', function () { } ] }, function (data) { - lint.assert.equal(5, data.warningCount); + lint.assert.equal(6, data.warningCount); done(); }); }); @@ -94,7 +94,7 @@ describe('no duplicate properties - sass', function () { } ] }, function (data) { - lint.assert.equal(4, data.warningCount); + lint.assert.equal(5, data.warningCount); done(); }); }); diff --git a/tests/rules/no-url-domains.js b/tests/rules/no-url-domains.js new file mode 100644 index 00000000..2caabdb1 --- /dev/null +++ b/tests/rules/no-url-domains.js @@ -0,0 +1,35 @@ +'use strict'; + +var lint = require('./_lint'); + +////////////////////////////// +// SCSS syntax tests +////////////////////////////// +describe('no url domains - scss', function () { + var file = lint.file('no-url-domains.scss'); + + it('enforce', function (done) { + lint.test(file, { + 'no-url-domains': 1 + }, function (data) { + lint.assert.equal(3, data.warningCount); + done(); + }); + }); +}); + +////////////////////////////// +// Sass syntax tests +////////////////////////////// +describe('no url domains - sass', function () { + var file = lint.file('no-url-domains.sass'); + + it('enforce', function (done) { + lint.test(file, { + 'no-url-domains': 1 + }, function (data) { + lint.assert.equal(3, data.warningCount); + done(); + }); + }); +}); diff --git a/tests/rules/no-url-protocols.js b/tests/rules/no-url-protocols.js index 87b5422a..65b5b5f1 100644 --- a/tests/rules/no-url-protocols.js +++ b/tests/rules/no-url-protocols.js @@ -16,8 +16,23 @@ describe('no url protocols - scss', function () { done(); }); }); + + it('[allow-protocol-relative-urls: true]', function (done) { + lint.test(file, { + 'no-url-protocols': [ + 1, + { + 'allow-protocol-relative-urls': true + } + ] + }, function (data) { + lint.assert.equal(2, data.warningCount); + done(); + }); + }); }); + ////////////////////////////// // Sass syntax tests ////////////////////////////// @@ -32,4 +47,18 @@ describe('no url protocols - sass', function () { done(); }); }); + + it('[allow-protocol-relative-urls: true]', function (done) { + lint.test(file, { + 'no-url-protocols': [ + 1, + { + 'allow-protocol-relative-urls': true + } + ] + }, function (data) { + lint.assert.equal(2, data.warningCount); + done(); + }); + }); }); diff --git a/tests/rules/placeholder-name-format.js b/tests/rules/placeholder-name-format.js index a5f8e40d..0d30033e 100644 --- a/tests/rules/placeholder-name-format.js +++ b/tests/rules/placeholder-name-format.js @@ -69,7 +69,7 @@ describe('placeholder name format - scss', function () { } ] }, function (data) { - lint.assert.equal(13, data.warningCount); + lint.assert.equal(9, data.warningCount); done(); }); }); @@ -185,7 +185,7 @@ describe('placeholder name format - sass', function () { } ] }, function (data) { - lint.assert.equal(13, data.warningCount); + lint.assert.equal(9, data.warningCount); done(); }); }); diff --git a/tests/rules/shorthand-values.js b/tests/rules/shorthand-values.js index e80cc21c..5bf8dbe7 100644 --- a/tests/rules/shorthand-values.js +++ b/tests/rules/shorthand-values.js @@ -12,7 +12,7 @@ describe('shorthand values - scss', function () { lint.test(file, { 'shorthand-values': 1 }, function (data) { - lint.assert.equal(77, data.warningCount); + lint.assert.equal(78, data.warningCount); done(); }); }); @@ -44,7 +44,7 @@ describe('shorthand values - scss', function () { } ] }, function (data) { - lint.assert.equal(39, data.warningCount); + lint.assert.equal(40, data.warningCount); done(); }); }); @@ -60,7 +60,7 @@ describe('shorthand values - scss', function () { } ] }, function (data) { - lint.assert.equal(46, data.warningCount); + lint.assert.equal(47, data.warningCount); done(); }); }); @@ -92,7 +92,7 @@ describe('shorthand values - scss', function () { } ] }, function (data) { - lint.assert.equal(58, data.warningCount); + lint.assert.equal(59, data.warningCount); done(); }); }); @@ -109,7 +109,7 @@ describe('shorthand values - scss', function () { } ] }, function (data) { - lint.assert.equal(65, data.warningCount); + lint.assert.equal(66, data.warningCount); done(); }); }); @@ -126,7 +126,7 @@ describe('shorthand values - scss', function () { } ] }, function (data) { - lint.assert.equal(58, data.warningCount); + lint.assert.equal(59, data.warningCount); done(); }); }); @@ -144,7 +144,7 @@ describe('shorthand values - scss', function () { } ] }, function (data) { - lint.assert.equal(77, data.warningCount); + lint.assert.equal(78, data.warningCount); done(); }); }); @@ -161,7 +161,7 @@ describe('shorthand values - sass', function () { lint.test(file, { 'shorthand-values': 1 }, function (data) { - lint.assert.equal(77, data.warningCount); + lint.assert.equal(78, data.warningCount); done(); }); }); @@ -193,7 +193,7 @@ describe('shorthand values - sass', function () { } ] }, function (data) { - lint.assert.equal(39, data.warningCount); + lint.assert.equal(40, data.warningCount); done(); }); }); @@ -209,7 +209,7 @@ describe('shorthand values - sass', function () { } ] }, function (data) { - lint.assert.equal(46, data.warningCount); + lint.assert.equal(47, data.warningCount); done(); }); }); @@ -241,7 +241,7 @@ describe('shorthand values - sass', function () { } ] }, function (data) { - lint.assert.equal(58, data.warningCount); + lint.assert.equal(59, data.warningCount); done(); }); }); @@ -258,7 +258,7 @@ describe('shorthand values - sass', function () { } ] }, function (data) { - lint.assert.equal(65, data.warningCount); + lint.assert.equal(66, data.warningCount); done(); }); }); @@ -275,7 +275,7 @@ describe('shorthand values - sass', function () { } ] }, function (data) { - lint.assert.equal(58, data.warningCount); + lint.assert.equal(59, data.warningCount); done(); }); }); @@ -293,7 +293,7 @@ describe('shorthand values - sass', function () { } ] }, function (data) { - lint.assert.equal(77, data.warningCount); + lint.assert.equal(78, data.warningCount); done(); }); }); diff --git a/tests/rules/variable-name-format.js b/tests/rules/variable-name-format.js index 9d3cf406..ecd647f7 100644 --- a/tests/rules/variable-name-format.js +++ b/tests/rules/variable-name-format.js @@ -27,7 +27,7 @@ describe('variable name format - scss', function () { } ] }, function (data) { - lint.assert.equal(15, data.warningCount); + lint.assert.equal(16, data.warningCount); done(); }); }); @@ -41,7 +41,7 @@ describe('variable name format - scss', function () { } ] }, function (data) { - lint.assert.equal(16, data.warningCount); + lint.assert.equal(17, data.warningCount); done(); }); }); @@ -55,7 +55,7 @@ describe('variable name format - scss', function () { } ] }, function (data) { - lint.assert.equal(10, data.warningCount); + lint.assert.equal(11, data.warningCount); done(); }); }); @@ -69,7 +69,7 @@ describe('variable name format - scss', function () { } ] }, function (data) { - lint.assert.equal(12, data.warningCount); + lint.assert.equal(9, data.warningCount); done(); }); }); @@ -98,7 +98,7 @@ describe('variable name format - scss', function () { } ] }, function (data) { - lint.assert.equal(16, data.warningCount); + lint.assert.equal(17, data.warningCount); done(); }); }); @@ -143,7 +143,7 @@ describe('variable name format - sass', function () { } ] }, function (data) { - lint.assert.equal(15, data.warningCount); + lint.assert.equal(16, data.warningCount); done(); }); }); @@ -157,7 +157,7 @@ describe('variable name format - sass', function () { } ] }, function (data) { - lint.assert.equal(10, data.warningCount); + lint.assert.equal(11, data.warningCount); done(); }); }); @@ -171,7 +171,7 @@ describe('variable name format - sass', function () { } ] }, function (data) { - lint.assert.equal(16, data.warningCount); + lint.assert.equal(17, data.warningCount); done(); }); }); @@ -185,7 +185,7 @@ describe('variable name format - sass', function () { } ] }, function (data) { - lint.assert.equal(12, data.warningCount); + lint.assert.equal(9, data.warningCount); done(); }); }); @@ -214,7 +214,7 @@ describe('variable name format - sass', function () { } ] }, function (data) { - lint.assert.equal(16, data.warningCount); + lint.assert.equal(17, data.warningCount); done(); }); }); diff --git a/tests/sass/border-zero.scss b/tests/sass/border-zero.scss index 398d1cb8..5336c600 100644 --- a/tests/sass/border-zero.scss +++ b/tests/sass/border-zero.scss @@ -21,3 +21,9 @@ .norf { border: 1px; } + +.norf { + // sass-lint:disable border-zero + border: none; + // sass-lint:enable border-zero +} diff --git a/tests/sass/class-name-format.sass b/tests/sass/class-name-format.sass index 07a34e9e..f63b1fe9 100644 --- a/tests/sass/class-name-format.sass +++ b/tests/sass/class-name-format.sass @@ -81,3 +81,16 @@ .APascalCase .camelCase color: red + +// Issue #872 - incorrect strict bem regex +.strict-bem__elem_bool + color: red + +.strict-bem__elem_bool-modifier + color: red + +.strict-bem__elem_key-val + color: red + +.strict-bem__elem_key--fail + color: red diff --git a/tests/sass/class-name-format.scss b/tests/sass/class-name-format.scss index 3a3af0c2..bbb2452e 100644 --- a/tests/sass/class-name-format.scss +++ b/tests/sass/class-name-format.scss @@ -116,3 +116,20 @@ } } } + +// Issue #872 - incorrect strict bem regex +.strict-bem__elem_bool { + color: red; +} + +.strict-bem__elem_bool-modifier { + color: red; +} + +.strict-bem__elem_key-val { + color: red; +} + +.strict-bem__elem_key--fail { + color: red; +} diff --git a/tests/sass/declarations-before-nesting.sass b/tests/sass/declarations-before-nesting.sass new file mode 100644 index 00000000..00bbb6a9 --- /dev/null +++ b/tests/sass/declarations-before-nesting.sass @@ -0,0 +1,27 @@ +.bar + content: 'baz' + + .qux + content: 'baz' + +.foo + .bar + content: 'where' + + content: 'baz' + + .baz + content: 'where' + + content: 'baz' + +.foo + .bar + content: 'where' + + .baz + content: 'quz' + + content: 'baz' + + content: 'baz' diff --git a/tests/sass/declarations-before-nesting.scss b/tests/sass/declarations-before-nesting.scss new file mode 100644 index 00000000..efb31d67 --- /dev/null +++ b/tests/sass/declarations-before-nesting.scss @@ -0,0 +1,35 @@ +.bar { + content: 'baz'; + + .qux { + content: 'baz'; + } +} + +.foo { + .bar { + content: 'where'; + } + + content: 'baz'; + + .baz { + content: 'where'; + } + + content: 'baz'; +} + +.foo { + .bar { + content: 'where'; + + .baz { + content: 'quz'; + } + + content: 'baz'; + } + + content: 'baz'; +} diff --git a/tests/sass/empty-file.sass b/tests/sass/empty-file.sass new file mode 100644 index 00000000..e69de29b diff --git a/tests/sass/front-matter/front-matter.scss b/tests/sass/front-matter/front-matter.scss new file mode 100644 index 00000000..93f12e57 --- /dev/null +++ b/tests/sass/front-matter/front-matter.scss @@ -0,0 +1,7 @@ +--- +# Only the main Sass file needs front matter (the dashes are enough) +--- + +.test { + color: red; +} diff --git a/tests/sass/max-file-line-count.sass b/tests/sass/max-file-line-count.sass new file mode 100644 index 00000000..26d1540f --- /dev/null +++ b/tests/sass/max-file-line-count.sass @@ -0,0 +1,301 @@ +// ============================================ +// +// Really +// long +// comment +// for +// padding +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +.test + .other + .foo + .bar + .baz + .qux + color: red diff --git a/tests/sass/max-file-line-count.scss b/tests/sass/max-file-line-count.scss new file mode 100644 index 00000000..6319aa21 --- /dev/null +++ b/tests/sass/max-file-line-count.scss @@ -0,0 +1,301 @@ +// ============================================ +// +// Really +// long +// comment +// for +// padding +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +.test { + .other { + .foo { + .bar { + .baz { + .qux { + color: red; + } + } + } + } + } +} diff --git a/tests/sass/max-line-length.sass b/tests/sass/max-line-length.sass new file mode 100644 index 00000000..a753bb7f --- /dev/null +++ b/tests/sass/max-line-length.sass @@ -0,0 +1,20 @@ +.really--long--class-name--that-unfortunately--isnt--very--succint--and-looks-stupid + color: red + +@function($aReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongVariableName) + @return 'test' + +// ============================================================================== +// +// This comment is too long clearly, we should probably make sure we have a rule to +// determine when we breach this length +// +// ============================================================================== + + +// ============================================================================= +// +// This comment comment on the other hand should be the perfect length, unless a +// user decides to make their max line length === 79! +// +// ============================================================================= diff --git a/tests/sass/max-line-length.scss b/tests/sass/max-line-length.scss new file mode 100644 index 00000000..07021d26 --- /dev/null +++ b/tests/sass/max-line-length.scss @@ -0,0 +1,22 @@ +.really--long--class-name--that-unfortunately--isnt--very--succint--and-looks-stupid { + color: red; +} + +@function($aReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongVariableName) { + @return 'test'; +} + +// ============================================================================== +// +// This comment is too long clearly, we should probably make sure we have a rule to +// determine when we breach this length +// +// ============================================================================== + + +// ============================================================================= +// +// This comment comment on the other hand should be the perfect length, unless a +// user decides to make their max line length === 79! +// +// ============================================================================= diff --git a/tests/sass/no-duplicate-properties.sass b/tests/sass/no-duplicate-properties.sass index 1f3b4c5b..fd4bb85c 100644 --- a/tests/sass/no-duplicate-properties.sass +++ b/tests/sass/no-duplicate-properties.sass @@ -30,3 +30,13 @@ content: 'display' display: flex display: inline-block + +// issue #907 - interpolation/variable in property names +$paint-border-1: top +$paint-border-2: left + +.test + border: $size solid transparent + border-#{$paint-border-1}: $size solid $color + border-#{$paint-border-2}: $size solid $color + border-#{$paint-border-2}: $size solid $color diff --git a/tests/sass/no-duplicate-properties.scss b/tests/sass/no-duplicate-properties.scss index af11b692..eb9869c2 100644 --- a/tests/sass/no-duplicate-properties.scss +++ b/tests/sass/no-duplicate-properties.scss @@ -37,3 +37,14 @@ display: inline-block; } + +// issue #907 - interpolation/variable in property names +$paint-border-1: top; +$paint-border-2: left; + +.test { + border: $size solid transparent; + border-#{$paint-border-1}: $size solid $color; + border-#{$paint-border-2}: $size solid $color; + border-#{$paint-border-2}: $size solid $color; +} diff --git a/tests/sass/no-url-domains.sass b/tests/sass/no-url-domains.sass new file mode 100644 index 00000000..21a17035 --- /dev/null +++ b/tests/sass/no-url-domains.sass @@ -0,0 +1,25 @@ +.foo + background-image: url('https://foo.com/img/bar.png') + + +.foo + background-image: url('http://foo.com/img/bar.png') + + +.foo + background-image: url('//foo.com/img/bar.png') + + +.foo + background-image: url('/img/bar.png') + + +.foo + background-image: url('img/bar.png') + + +.foo + background-image: url('bar.png') + +.foo + background-image: url('data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7') diff --git a/tests/sass/no-url-domains.scss b/tests/sass/no-url-domains.scss new file mode 100644 index 00000000..98ba24c0 --- /dev/null +++ b/tests/sass/no-url-domains.scss @@ -0,0 +1,27 @@ +.foo { + background-image: url('https://foo.com/img/bar.png'); +} + +.foo { + background-image: url('http://foo.com/img/bar.png'); +} + +.foo { + background-image: url('//foo.com/img/bar.png'); +} + +.foo { + background-image: url('/img/bar.png'); +} + +.foo { + background-image: url('img/bar.png'); +} + +.foo { + background-image: url('bar.png'); +} + +.foo { + background-image: url('data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'); +} diff --git a/tests/sass/ruleToggler-disable-a-block.sass b/tests/sass/ruleToggler-disable-a-block.sass new file mode 100644 index 00000000..79981c64 --- /dev/null +++ b/tests/sass/ruleToggler-disable-a-block.sass @@ -0,0 +1,2 @@ +p + // sass-lint:disable-block a diff --git a/tests/sass/ruleToggler-disable-a-block.scss b/tests/sass/ruleToggler-disable-a-block.scss new file mode 100644 index 00000000..8eddb3ea --- /dev/null +++ b/tests/sass/ruleToggler-disable-a-block.scss @@ -0,0 +1,3 @@ +p { + // sass-lint:disable-block a +} diff --git a/tests/sass/ruleToggler-disable-a-line.sass b/tests/sass/ruleToggler-disable-a-line.sass new file mode 100644 index 00000000..9c449198 --- /dev/null +++ b/tests/sass/ruleToggler-disable-a-line.sass @@ -0,0 +1,2 @@ +p + border-color: red // sass-lint:disable-line a diff --git a/tests/sass/ruleToggler-disable-a-line.scss b/tests/sass/ruleToggler-disable-a-line.scss new file mode 100644 index 00000000..7d6ca7cc --- /dev/null +++ b/tests/sass/ruleToggler-disable-a-line.scss @@ -0,0 +1,3 @@ +p { + border-color: red; // sass-lint:disable-line a +} diff --git a/tests/sass/ruleToggler-disable-a-rule.sass b/tests/sass/ruleToggler-disable-a-rule.sass new file mode 100644 index 00000000..5f04af37 --- /dev/null +++ b/tests/sass/ruleToggler-disable-a-rule.sass @@ -0,0 +1 @@ +// sass-lint:disable a diff --git a/tests/sass/ruleToggler-disable-a-rule.scss b/tests/sass/ruleToggler-disable-a-rule.scss new file mode 100644 index 00000000..5f04af37 --- /dev/null +++ b/tests/sass/ruleToggler-disable-a-rule.scss @@ -0,0 +1 @@ +// sass-lint:disable a diff --git a/tests/sass/ruleToggler-disable-all-then-reenable.sass b/tests/sass/ruleToggler-disable-all-then-reenable.sass new file mode 100644 index 00000000..63eeb627 --- /dev/null +++ b/tests/sass/ruleToggler-disable-all-then-reenable.sass @@ -0,0 +1,3 @@ +// sass-lint:disable-all + +// sass-lint:enable-all diff --git a/tests/sass/ruleToggler-disable-all-then-reenable.scss b/tests/sass/ruleToggler-disable-all-then-reenable.scss new file mode 100644 index 00000000..63eeb627 --- /dev/null +++ b/tests/sass/ruleToggler-disable-all-then-reenable.scss @@ -0,0 +1,3 @@ +// sass-lint:disable-all + +// sass-lint:enable-all diff --git a/tests/sass/ruleToggler-disable-all.sass b/tests/sass/ruleToggler-disable-all.sass new file mode 100644 index 00000000..1776554a --- /dev/null +++ b/tests/sass/ruleToggler-disable-all.sass @@ -0,0 +1 @@ +// sass-lint:disable-all diff --git a/tests/sass/ruleToggler-disable-all.scss b/tests/sass/ruleToggler-disable-all.scss new file mode 100644 index 00000000..1776554a --- /dev/null +++ b/tests/sass/ruleToggler-disable-all.scss @@ -0,0 +1 @@ +// sass-lint:disable-all diff --git a/tests/sass/ruleToggler-disable-multiple-rules.sass b/tests/sass/ruleToggler-disable-multiple-rules.sass new file mode 100644 index 00000000..8e44d373 --- /dev/null +++ b/tests/sass/ruleToggler-disable-multiple-rules.sass @@ -0,0 +1 @@ +// sass-lint:disable a b, c d diff --git a/tests/sass/ruleToggler-disable-multiple-rules.scss b/tests/sass/ruleToggler-disable-multiple-rules.scss new file mode 100644 index 00000000..8e44d373 --- /dev/null +++ b/tests/sass/ruleToggler-disable-multiple-rules.scss @@ -0,0 +1 @@ +// sass-lint:disable a b, c d diff --git a/tests/sass/ruleToggler-disable-then-enable.sass b/tests/sass/ruleToggler-disable-then-enable.sass new file mode 100644 index 00000000..5fdc7cf4 --- /dev/null +++ b/tests/sass/ruleToggler-disable-then-enable.sass @@ -0,0 +1,5 @@ +p + // sass-lint:disable a + border: none + // sass-lint:enable a + color: blue diff --git a/tests/sass/ruleToggler-disable-then-enable.scss b/tests/sass/ruleToggler-disable-then-enable.scss new file mode 100644 index 00000000..44f55f67 --- /dev/null +++ b/tests/sass/ruleToggler-disable-then-enable.scss @@ -0,0 +1,6 @@ +p { + // sass-lint:disable a + border: none; + // sass-lint:enable a + color: blue; +} diff --git a/tests/sass/ruleToggler-empty-comment.sass b/tests/sass/ruleToggler-empty-comment.sass new file mode 100644 index 00000000..eea459d1 --- /dev/null +++ b/tests/sass/ruleToggler-empty-comment.sass @@ -0,0 +1,2 @@ +p + /**/ diff --git a/tests/sass/ruleToggler-empty-comment.scss b/tests/sass/ruleToggler-empty-comment.scss new file mode 100644 index 00000000..1d7676a4 --- /dev/null +++ b/tests/sass/ruleToggler-empty-comment.scss @@ -0,0 +1,3 @@ +p { + /**/ +} \ No newline at end of file diff --git a/tests/sass/ruleToggler-guarantee-order.sass b/tests/sass/ruleToggler-guarantee-order.sass new file mode 100644 index 00000000..62efa91c --- /dev/null +++ b/tests/sass/ruleToggler-guarantee-order.sass @@ -0,0 +1,17 @@ +p + // sass-lint:disable a + border: 0 + // sass-lint:disable-block a + font-size: 100% + +a + // sass-lint:disable a + border: 0 + // sass-lint:disable-block a + font-size: 100% + +li + /* sass-lint:disable a + /* sass-lint:disable a + border: 0 + font-size: 100% diff --git a/tests/sass/ruleToggler-guarantee-order.scss b/tests/sass/ruleToggler-guarantee-order.scss new file mode 100644 index 00000000..7c6d6178 --- /dev/null +++ b/tests/sass/ruleToggler-guarantee-order.scss @@ -0,0 +1,17 @@ +p { + // sass-lint:disable a + border: 0; + // sass-lint:disable-block a + font-size: 100%; +} + +a { // sass-lint:disable a + border: 0; + // sass-lint:disable-block a + font-size: 100%; +} + +li { /* sass-lint:disable a */ /* sass-lint:disable a */ + border: 0; + font-size: 100%; +} diff --git a/tests/sass/ruleToggler-ignore-unknown.sass b/tests/sass/ruleToggler-ignore-unknown.sass new file mode 100644 index 00000000..7913ac83 --- /dev/null +++ b/tests/sass/ruleToggler-ignore-unknown.sass @@ -0,0 +1,2 @@ +p + //sass-lint:random a diff --git a/tests/sass/ruleToggler-ignore-unknown.scss b/tests/sass/ruleToggler-ignore-unknown.scss new file mode 100644 index 00000000..698ec2e5 --- /dev/null +++ b/tests/sass/ruleToggler-ignore-unknown.scss @@ -0,0 +1,3 @@ +p { + //sass-lint:random a +} \ No newline at end of file diff --git a/tests/sass/shorthand-values.sass b/tests/sass/shorthand-values.sass index b766c6ce..6e9dbffd 100644 --- a/tests/sass/shorthand-values.sass +++ b/tests/sass/shorthand-values.sass @@ -305,3 +305,10 @@ .test border-color: transparent #095b97 transparent #095b97 + +// Issue #847 - Ignoring function arguments +.foo + padding: 0 size('half-shim') 0 size('spacer') + +.foo + padding: 0 size('half-shim') 0 size('half-shim') diff --git a/tests/sass/shorthand-values.scss b/tests/sass/shorthand-values.scss index 7ffbccfb..54a422a4 100644 --- a/tests/sass/shorthand-values.scss +++ b/tests/sass/shorthand-values.scss @@ -369,3 +369,12 @@ .test { border-color: transparent #095b97 transparent #095b97; } + +// Issue #847 - Ignoring function arguments +.foo { + padding: 0 size('half-shim') 0 size('spacer'); +} + +.foo { + padding: 0 size('half-shim') 0 size('half-shim'); +} diff --git a/tests/sass/success.scss b/tests/sass/success.scss new file mode 100644 index 00000000..ae771e48 --- /dev/null +++ b/tests/sass/success.scss @@ -0,0 +1,3 @@ +.one { + margin-top: 0; +} diff --git a/tests/sass/variable-name-format.sass b/tests/sass/variable-name-format.sass index 3c3ce96f..9738a0a7 100644 --- a/tests/sass/variable-name-format.sass +++ b/tests/sass/variable-name-format.sass @@ -19,3 +19,7 @@ $_does_NOT-fitSTANDARD: 1 .class width: $snake_case + +// Issue #901 - operators not recognized as separate tokens by gonzales-pe +.content-main + padding: $mobile-site-gutter*1.5 diff --git a/tests/sass/variable-name-format.scss b/tests/sass/variable-name-format.scss index fd8df367..e04be3ae 100644 --- a/tests/sass/variable-name-format.scss +++ b/tests/sass/variable-name-format.scss @@ -20,3 +20,8 @@ $_does_NOT-fitSTANDARD: 1; .class { width: $snake_case; } + +// Issue #901 - operators not recognized as separate tokens by gonzales-pe +.content-main { + padding: $mobile-site-gutter*1.5; +} diff --git a/tests/yml/.indentation-error.yml b/tests/yml/.indentation-error.yml new file mode 100644 index 00000000..57dbbfe6 --- /dev/null +++ b/tests/yml/.indentation-error.yml @@ -0,0 +1,2 @@ +rules: + indentation: 2 diff --git a/tests/yml/.indentation-warn.yml b/tests/yml/.indentation-warn.yml new file mode 100644 index 00000000..71a1f08f --- /dev/null +++ b/tests/yml/.indentation-warn.yml @@ -0,0 +1,2 @@ +rules: + indentation: 1 diff --git a/tests/yml/.max-0-warnings.yml b/tests/yml/.max-0-warnings.yml new file mode 100644 index 00000000..0a3dc120 --- /dev/null +++ b/tests/yml/.max-0-warnings.yml @@ -0,0 +1,2 @@ +options: + max-warnings: 0 diff --git a/tests/yml/.max-10-warnings.yml b/tests/yml/.max-10-warnings.yml new file mode 100644 index 00000000..85b0f1c9 --- /dev/null +++ b/tests/yml/.max-10-warnings.yml @@ -0,0 +1,2 @@ +options: + max-warnings: 10 diff --git a/tests/yml/.max-100-warnings.yml b/tests/yml/.max-100-warnings.yml new file mode 100644 index 00000000..759639a6 --- /dev/null +++ b/tests/yml/.max-100-warnings.yml @@ -0,0 +1,2 @@ +options: + max-warnings: 100