Repository: sasstools/sass-lint
Pull request #491 (2016-01-28 19:22:45)

Release/1.5.0


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2fe1217..ecc4c8d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,24 @@
 # Sass Lint Changelog
 
+## v1.5.0
+**January 28, 2016**
+
+New year blues
+
+**Changes**
+* AST parse errors will now be returned to the user as `Fatal` lint errors this prevents un-handled errors breaking builds [#459](https://github.com/sasstools/sass-lint/pull/459)
+* Sass-lint plugin for Brackets added to the README [#470](https://github.com/sasstools/sass-lint/issues/470)
+* Sass-lint plugin for IntelliJ IDEA, RubyMine, WebStorm, PhpStorm, PyCharm, added to the README [#484](https://github.com/sasstools/sass-lint/issues/484)
+
+
+**CLI**
+* Updated error codes, whenever errors are present even when cli is using the `--no-exit` flag a error code of 1 will be output [#221](https://github.com/sasstools/sass-lint/issues/221)
+
+**Fixes**
+* Fixed an issue where an error of `next is undefined` would be thrown in the `space-after-colon` rule [#468](https://github.com/sasstools/sass-lint/issues/468)
+* Fixed an issue with negative z-index values in the `space-around-operator` rule [#454](https://github.com/sasstools/sass-lint/issues/454)
+* Fixed another minor issue with `space-around-operator` to prevent a possible crash [#483](https://github.com/sasstools/sass-lint/issues/483)
+
 ## v1.4.0
 **December 10, 2015**
 
@@ -39,6 +58,7 @@ The long overdue update!
 * [Dan Purdy](https://github.com/DanPurdy)
 * [Ben Rothman](https://github.com/benthemonkey)
 * [Don Abrams](https://github.com/donabrams)
+* [Andrew Hays](https://github.com/Dru89)
 * [Kaelig](https://github.com/kaelig)
 
 **A big thankyou to everyone who reported issues or contributed to the discussion around issues**
diff --git a/README.md b/README.md
index 76122db..46d3585 100644
--- a/README.md
+++ b/README.md
@@ -54,3 +54,5 @@ Our AST is [Gonzales-PE](https://github.com/tonyganch/gonzales-pe/tree/dev). Eac
 
 * [Atom](https://atom.io/packages/linter-sass-lint)
 * [Sublime Text](https://github.com/skovhus/SublimeLinter-contrib-sass-lint)
+* [Brackets](https://github.com/petetnt/brackets-sass-lint)
+* [IntelliJ IDEA, RubyMine, WebStorm, PhpStorm, PyCharm](https://github.com/idok/sass-lint-plugin)
diff --git a/bin/sass-lint.js b/bin/sass-lint.js
index afaf34c..33bea9d 100755
--- a/bin/sass-lint.js
+++ b/bin/sass-lint.js
@@ -7,7 +7,8 @@ var program = require('commander'),
 
 var configPath,
     ignores,
-    configOptions = {};
+    configOptions = {},
+    exitCode = 0;
 
 var detectPattern = function (pattern) {
   var detects;
@@ -18,6 +19,10 @@ var detectPattern = function (pattern) {
     lint.outputResults(detects, configOptions, configPath);
   }
 
+  if (lint.errorCount(detects).count) {
+    exitCode = 1;
+  }
+
   if (program.exit) {
     lint.failOnError(detects);
   }
@@ -86,3 +91,7 @@ else {
     detectPattern(path);
   });
 }
+
+process.on('exit', function () {
+  process.exit(exitCode); // eslint-disable-line no-process-exit
+});
diff --git a/index.js b/index.js
index 9888eb8..787f074 100644
--- a/index.js
+++ b/index.js
@@ -6,40 +6,119 @@ var slConfig = require('./lib/config'),
     slRules = require('./lib/rules'),
     glob = require('glob'),
     path = require('path'),
-    jsonFormatter = require('eslint/lib/formatters/json'),
     fs = require('fs-extra');
 
-
 var sassLint = function (config) {
   config = require('./lib/config')(config);
   return;
 };
 
+/**
+ * Takes any user specified options and a configPath
+ * which returns a compiled config object
+ *
+ * @param {object} config user specified rules/options passed in
+ * @param {string} configPath path to a config file
+ * @returns {object} the compiled config object
+ */
 sassLint.getConfig = function (config, configPath) {
   return slConfig(config, configPath);
 };
 
-sassLint.resultCount = function (results) {
-  var flagCount = 0,
-      jsonResults = JSON.parse(jsonFormatter(results));
+/**
+ * Parses our results object to count errors and return
+ * paths to files with detected errors.
+ *
+ * @param {object} results our results object
+ * @returns {object} errors object containing the error count and paths for files incl. errors
+ */
+sassLint.errorCount = function (results) {
+  var errors = {
+    count: 0,
+    files: []
+  };
 
+  results.forEach(function (result) {
+    if (result.errorCount) {
+      errors.count += result.errorCount;
+      errors.files.push(result.filePath);
+    }
+  });
 
-  for (var i = 0; i < jsonResults.length; i++) {
-    flagCount += (jsonResults[i].warningCount + jsonResults[i].errorCount);
-  }
+  return errors;
+};
+
+/**
+ * Parses our results object to count warnings and return
+ * paths to files with detected warnings.
+ *
+ * @param {object} results our results object
+ * @returns {object} warnings object containing the error count and paths for files incl. warnings
+ */
+sassLint.warningCount = function (results) {
+  var warnings = {
+    count: 0,
+    files: []
+  };
+
+  results.forEach(function (result) {
+    if (result.warningCount) {
+      warnings.count += result.warningCount;
+      warnings.files.push(result.filePath);
+    }
+  });
 
-  return flagCount;
+  return warnings;
 };
 
+/**
+ * Parses our results object to count warnings and errors and return
+ * a cumulative count of both
+ *
+ * @param {object} results our results object
+ * @returns {int} the cumulative count of errors and warnings detected
+ */
+sassLint.resultCount = function (results) {
+  var warnings = this.warningCount(results),
+      errors = this.errorCount(results);
+
+  return warnings.count + errors.count;
+};
+
+/**
+ * Runs each rule against our AST tree and returns our main object of detected
+ * errors, warnings, messages and filenames.
+ *
+ * @param {object} file file object from fs.readFileSync
+ * @param {object} options user specified rules/options passed in
+ * @param {string} configPath path to a config file
+ * @returns {object} an object containing error & warning counts plus lint messages for each parsed file
+ */
 sassLint.lintText = function (file, options, configPath) {
   var rules = slRules(this.getConfig(options, configPath)),
-      ast = groot(file.text, file.format, file.filename),
+      ast = {},
       detects,
       results = [],
       errors = 0,
       warnings = 0;
 
-  if (ast.content.length > 0) {
+  try {
+    ast = groot(file.text, file.format, file.filename);
+  }
+  catch (e) {
+    var line = e.line || 1;
+    errors++;
+
+    results = [{
+      ruleId: 'Fatal',
+      line: line,
+      column: 1,
+      message: e.message,
+      severity: 2
+    }];
+  }
+
+  if (ast.content && ast.content.length > 0) {
     rules.forEach(function (rule) {
       detects = rule.rule.detect(ast, rule);
       results = results.concat(detects);
@@ -54,7 +133,6 @@ sassLint.lintText = function (file, options, configPath) {
     });
   }
 
-
   results.sort(helpers.sortDetects);
 
   return {
@@ -65,6 +143,16 @@ sassLint.lintText = function (file, options, configPath) {
   };
 };
 
+/**
+ * Takes a glob pattern or target string and creates an array of files as targets for
+ * linting taking into account any user specified ignores. For each resulting file sassLint.lintText
+ * is called which returns an object of results for that file which we push to our results object.
+ *
+ * @param {string} files a glob pattern or single file path as a lint target
+ * @param {object} options user specified rules/options passed in
+ * @param {string} configPath path to a config file
+ * @returns {object} results object containing all results
+ */
 sassLint.lintFiles = function (files, options, configPath) {
   var that = this,
       results = [],
@@ -99,7 +187,14 @@ sassLint.lintFiles = function (files, options, configPath) {
   return results;
 };
 
-
+/**
+ * Handles formatting of results using EsLint formatters
+ *
+ * @param {object} results our results object
+ * @param {object} options user specified rules/options passed in
+ * @param {string} configPath path to a config file
+ * @returns {object} results our results object in the user specified format
+ */
 sassLint.format = function (results, options, configPath) {
   var config = this.getConfig(options, configPath),
       format = config.options.formatter.toLowerCase();
@@ -109,6 +204,15 @@ sassLint.format = function (results, options, configPath) {
   return formatted(results);
 };
 
+/**
+ * Handles outputting results whether this be straight to the console/stdout or to a file.
+ * Passes results to the format function to ensure results are output in the chosen format
+ *
+ * @param {object} results our results object
+ * @param {object} options user specified rules/options passed in
+ * @param {string} configPath path to a config file
+ * @returns {object} results our results object
+ */
 sassLint.outputResults = function (results, options, configPath) {
   var config = this.getConfig(options, configPath);
 
@@ -132,16 +236,18 @@ sassLint.outputResults = function (results, options, configPath) {
   return results;
 };
 
+/**
+ * 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
+ * @returns {void}
+ */
 sassLint.failOnError = function (results) {
-  var result,
-      i;
+  var errorCount = this.errorCount(results);
 
-  for (i = 0; i < results.length; i++) {
-    result = results[i];
-
-    if (result.errorCount > 0) {
-      throw new Error(result.errorCount + ' errors detected in ' + result.filePath);
-    }
+  if (errorCount.count > 0) {
+    throw new Error(errorCount.count + ' errors were detected in \n- ' + errorCount.files.join('\n- '));
   }
 };
 
diff --git a/lib/groot.js b/lib/groot.js
index 4a3e7de..f3bad09 100644
--- a/lib/groot.js
+++ b/lib/groot.js
@@ -6,8 +6,7 @@
 var gonzales = require('gonzales-pe');
 
 module.exports = function (text, syntax, filename) {
-  var fileInfo = filename ? ' at ' + filename : '',
-      tree;
+  var tree;
 
   // Run `.toString()` to allow Buffers to be passed in
   text = text.toString();
@@ -18,11 +17,20 @@ module.exports = function (text, syntax, filename) {
     });
   }
   catch (e) {
-    throw new Error('Parsing error' + fileInfo + ': ' + e.message);
+    throw {
+      message: e.message,
+      file: filename,
+      line: e.line
+    };
   }
 
   if (typeof tree === 'undefined') {
-    throw new Error('Undefined tree' + fileInfo + ': ' + text.toString() + ' => ' + tree.toString());
+    throw {
+      message: 'Undefined tree',
+      file: filename,
+      text: text.toString(),
+      tree: tree.toString()
+    };
   }
 
   return tree;
diff --git a/lib/rules.js b/lib/rules.js
index c7461b0..b4427ac 100644
--- a/lib/rules.js
+++ b/lib/rules.js
@@ -27,21 +27,30 @@ module.exports = function (config) {
   Object.keys(config.rules).forEach(function (rule) {
     var fullRule = config.rules[rule],
         loadRule,
+        severity,
         options,
         ruleSearch;
+
+    if (typeof fullRule === 'number') {
+      severity = fullRule;
+      options = {};
+    }
+    else {
+      severity = fullRule[0];
+      options = fullRule[1];
+    }
+
     // Only seek out rules that are enabled
-    if ((typeof fullRule === 'number' && fullRule !== 0) || (typeof fullRule === 'object' && fullRule[0] !== 0)) {
+    if (severity !== 0) {
       ruleSearch = searchArray(rules, rule);
       if (ruleSearch >= 0) {
         loadRule = require(rules[ruleSearch]);
 
-        options = typeof fullRule === 'object' ? fullRule[1] : {};
-
         options = merge.recursive(true, loadRule.defaults, options);
 
         handlers.push({
           'rule': loadRule,
-          'severity': typeof fullRule === 'number' ? fullRule : fullRule[0],
+          'severity': severity,
           'options': options
         });
       }
diff --git a/lib/rules/extends-before-declarations.js b/lib/rules/extends-before-declarations.js
index 395b992..8597be7 100644
--- a/lib/rules/extends-before-declarations.js
+++ b/lib/rules/extends-before-declarations.js
@@ -13,7 +13,8 @@ module.exports = {
       var lastDeclaration = null;
 
       block.forEach(function (item, j) {
-        if (item.type === 'include' || item.type === 'extend') {
+        if ((item.type === 'include' || item.type === 'extend') &&
+            item.first('atkeyword')) {
           if (item.first('atkeyword').first('ident').content === 'extend') {
             if (j > lastDeclaration && lastDeclaration !== null) {
               item.forEach('simpleSelector', function () {
diff --git a/lib/rules/space-after-colon.js b/lib/rules/space-after-colon.js
index 3425579..b5b4f9a 100644
--- a/lib/rules/space-after-colon.js
+++ b/lib/rules/space-after-colon.js
@@ -14,7 +14,7 @@ module.exports = {
       if (delimiter.content === ':') {
         var next = parent.content[i + 1];
 
-        if (next.is('space')) {
+        if (next && next.is('space')) {
           if (!parser.options.include) {
             result = helpers.addUnique(result, {
               'ruleId': parser.rule.name,
diff --git a/lib/rules/space-around-operator.js b/lib/rules/space-around-operator.js
index 57cf682..f01813b 100644
--- a/lib/rules/space-around-operator.js
+++ b/lib/rules/space-around-operator.js
@@ -44,6 +44,7 @@ var checkSpacing = function (node, i, parent, parser, result) {
     //////////////////////////
 
     if (parent && operator && next) {
+
       // The parent ofthe exceptions are always going to be of type value
       if (parent.is('value')) {
         // 1. We should allow valid CSS use of leading - operator when
@@ -55,7 +56,6 @@ var checkSpacing = function (node, i, parent, parser, result) {
         // 2. We should allow valid CSS use of / operators when defining
         // font size/line-height
         if (previous) {
-
           // The first value is the font-size and must have a unit, therefore
           // a node of type dimension
           if (operator === '/' && previous.is('dimension')
@@ -65,6 +65,12 @@ var checkSpacing = function (node, i, parent, parser, result) {
             return false;
           }
         }
+
+        // 3. If there is nothing leading the minus, it's a negative value so we
+        // shouldn't have a following space
+        if (!previous && operator === '-' && !next.is('space')) {
+          return false;
+        }
       }
     }
 
@@ -86,8 +92,8 @@ var checkSpacing = function (node, i, parent, parser, result) {
         }
         else {
           if (
-              ((previous.end.line >= previous.start.line) && (previous.end.column > previous.start.column))
-              || (next.end.line >= next.start.line) && (next.end.column > next.start.column)
+              (previous && (previous.end.line >= previous.start.line) && (previous.end.column > previous.start.column))
+              || (next && (next.end.line >= next.start.line) && (next.end.column > next.start.column))
             ) {
             result = helpers.addUnique(result, {
               'ruleId': parser.rule.name,
diff --git a/package.json b/package.json
index 9241efb..a30407f 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "sass-lint",
-  "version": "1.4.0",
+  "version": "1.5.0",
   "description": "All Node Sass linter!",
   "main": "index.js",
   "scripts": {


Test patch:
diff --git a/tests/cli.js b/tests/cli.js
index 5775ce1..a22f96c 100644
--- a/tests/cli.js
+++ b/tests/cli.js
@@ -307,4 +307,69 @@ 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/sass/cli-error.scss --verbose --no-exit';
+
+    childProcess.exec(command, function (err) {
+      if (err.code === 1) {
+        return done();
+      }
+
+      return done(new Error('Error code not 1'));
+    });
+  });
+
+  /**
+   * 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';
+
+    childProcess.exec(command, function (err, stdout) { // eslint-disable-line handle-callback-err
+      var result = JSON.parse(stdout)[0];
+
+      assert.equal(1, result.errorCount);
+      done();
+    });
+  });
+
+  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';
+
+    childProcess.exec(command, function (err, stdout) { // eslint-disable-line handle-callback-err
+      var result = JSON.parse(stdout)[0],
+          messages = result.messages[0],
+          severity = 2;
+
+      assert.equal(severity, messages.severity);
+      done();
+    });
+  });
+
+  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';
+
+    childProcess.exec(command, function (err, stdout) { // eslint-disable-line handle-callback-err
+      var result = JSON.parse(stdout)[0],
+          message = result.messages[0].message,
+          expected = 'Please check validity of the block starting from line #5';
+
+      assert.equal(expected, message);
+      done();
+    });
+  });
+
+  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';
+
+    childProcess.exec(command, function (err, stdout) { // eslint-disable-line handle-callback-err
+      var result = JSON.parse(stdout)[0],
+          messages = result.messages[0],
+          ruleId = 'Fatal';
+
+      assert.equal(ruleId, messages.ruleId);
+      done();
+    });
+  });
 });
diff --git a/tests/main.js b/tests/main.js
index 137494b..4aed580 100644
--- a/tests/main.js
+++ b/tests/main.js
@@ -12,6 +12,48 @@ var lintFile = function lintFile (file, options, cb) {
   cb(results[0]);
 };
 
+var resultsObj = [{
+  filePath: 'app/scss/echo-base/defaults/utilities/_mixins.scss',
+  warningCount: 3,
+  errorCount: 0,
+  messages: [{
+    ruleId: 'no-vendor-prefixes',
+    line: 120,
+    column: 8,
+    message: 'Vendor prefixes should not be used',
+    severity: 1
+  }, {
+    ruleId: 'no-vendor-prefixes',
+    line: 130,
+    column: 8,
+    message: 'Vendor prefixes should not be used',
+    severity: 1
+  }, {
+    ruleId: 'no-vendor-prefixes',
+    line: 140,
+    column: 8,
+    message: 'Vendor prefixes should not be used',
+    severity: 1
+  }]
+}, {
+  filePath: 'app/scss/main.scss',
+  warningCount: 0,
+  errorCount: 2,
+  messages: [{
+    ruleId: 'no-ids',
+    line: 52,
+    column: 1,
+    message: 'ID selectors not allowed',
+    severity: 2
+  }, {
+    ruleId: 'no-ids',
+    line: 57,
+    column: 1,
+    message: 'ID selectors not allowed',
+    severity: 2
+  }]
+}];
+
 describe('sass lint', function () {
   //////////////////////////////
   // Not Error on Empty Files
@@ -24,4 +66,82 @@ describe('sass lint', function () {
       done();
     });
   });
+
+  //////////////////////////////
+  // Parse Errors should return as lint errors
+  //////////////////////////////
+  it('Parse Errors should return as lint errors', function (done) {
+    lintFile('parse.scss', function (data) {
+      assert.equal(1, data.errorCount);
+      done();
+    });
+  });
+
+  it('Parse Errors should not include warnings too', function (done) {
+    lintFile('parse.scss', function (data) {
+      assert.equal(0, data.warningCount);
+      done();
+    });
+  });
+
+  it('Parse Errors should return as severity 2', function (done) {
+    lintFile('parse.scss', function (data) {
+      var severity = data.messages[0].severity;
+
+      assert.equal(2, severity);
+      done();
+    });
+  });
+
+  it('Parse Errors should return the correct error message', function (done) {
+    lintFile('parse.scss', function (data) {
+      var message = data.messages[0].message,
+          expected = 'Please check validity of the block starting from line #5';
+
+      assert.equal(expected, message);
+      done();
+    });
+  });
+
+  it('Parse Errors should return the rule ID \'Fatal\'', function (done) {
+    lintFile('parse.scss', function (data) {
+      var ruleId = data.messages[0].ruleId,
+          expected = 'Fatal';
+
+      assert.equal(expected, ruleId);
+      done();
+    });
+  });
+});
+
+describe('sassLint detect counts', function () {
+  //////////////////////////////
+  // Error count
+  //////////////////////////////
+  it('should equal 2 errors', function (done) {
+    var result = lint.errorCount(resultsObj);
+
+    assert.equal(2, result.count);
+    done();
+  });
+
+  //////////////////////////////
+  // Warning count
+  //////////////////////////////
+  it('should equal 3 warnings', function (done) {
+    var result = lint.warningCount(resultsObj);
+
+    assert.equal(3, result.count);
+    done();
+  });
+
+  //////////////////////////////
+  // Result count
+  //////////////////////////////
+  it('should equal 5 overall detects', function (done) {
+    var result = lint.resultCount(resultsObj);
+
+    assert.equal(5, result);
+    done();
+  });
 });
diff --git a/tests/rules/space-around-operator.js b/tests/rules/space-around-operator.js
index ca2f5f9..bd493a5 100644
--- a/tests/rules/space-around-operator.js
+++ b/tests/rules/space-around-operator.js
@@ -12,7 +12,7 @@ describe('space around operator - scss', function () {
     lint.test(file, {
       'space-around-operator': 1
     }, function (data) {
-      lint.assert.equal(83, data.warningCount);
+      lint.assert.equal(84, data.warningCount);
       done();
     });
   });
@@ -26,7 +26,7 @@ describe('space around operator - scss', function () {
         }
       ]
     }, function (data) {
-      lint.assert.equal(77, data.warningCount);
+      lint.assert.equal(78, data.warningCount);
       done();
     });
   });
@@ -42,7 +42,7 @@ describe('space around operator - sass', function () {
     lint.test(file, {
       'space-around-operator': 1
     }, function (data) {
-      lint.assert.equal(83, data.warningCount);
+      lint.assert.equal(84, data.warningCount);
       done();
     });
   });
@@ -56,7 +56,7 @@ describe('space around operator - sass', function () {
         }
       ]
     }, function (data) {
-      lint.assert.equal(77, data.warningCount);
+      lint.assert.equal(78, data.warningCount);
       done();
     });
   });
diff --git a/tests/sass/cli-error.scss b/tests/sass/cli-error.scss
new file mode 100644
index 0000000..1bc67ee
--- /dev/null
+++ b/tests/sass/cli-error.scss
@@ -0,0 +1,3 @@
+#cli {
+  color: red;
+}
diff --git a/tests/sass/extends-before-declarations.sass b/tests/sass/extends-before-declarations.sass
index bcac956..868b382 100644
--- a/tests/sass/extends-before-declarations.sass
+++ b/tests/sass/extends-before-declarations.sass
@@ -27,3 +27,8 @@
   .bar
     @extend %waldo
     content: 'where'
+
+
+.foo-noextend
+  +foo
+  content: "bar"
diff --git a/tests/sass/extends-before-declarations.scss b/tests/sass/extends-before-declarations.scss
index 41e15e1..818a88a 100644
--- a/tests/sass/extends-before-declarations.scss
+++ b/tests/sass/extends-before-declarations.scss
@@ -29,3 +29,7 @@
     content: 'where';
   }
 }
+
+.foo-noextend {
+  @include foo;
+}
diff --git a/tests/sass/parse.scss b/tests/sass/parse.scss
new file mode 100644
index 0000000..837044d
--- /dev/null
+++ b/tests/sass/parse.scss
@@ -0,0 +1,7 @@
+.one {
+  color: red;
+}
+
+#test
+  color: blue;
+}
diff --git a/tests/sass/space-around-operator.sass b/tests/sass/space-around-operator.sass
index b9da58e..2fbc4eb 100644
--- a/tests/sass/space-around-operator.sass
+++ b/tests/sass/space-around-operator.sass
@@ -447,3 +447,22 @@ $qux: (2 * 1) // FIXME: Gonzales - FIXED IN BETA
 
 h1
   +large-text
+
+.foo
+  z-index: -1
+
+// ========================
+// Interesting user cases
+// ========================
+
+// Should ignore the this
+.foo
+  $val: -($foo * 2)
+
+// Should flag this if rule is true
+.foo
+  $val: 1-($foo * 2)
+
+// Should flag this if rule is false
+.foo
+  $val: 1 - ($foo * 2)
diff --git a/tests/sass/space-around-operator.scss b/tests/sass/space-around-operator.scss
index 27287c9..1fe75a3 100644
--- a/tests/sass/space-around-operator.scss
+++ b/tests/sass/space-around-operator.scss
@@ -452,3 +452,31 @@ $qux: (2 * 1); // FIXME: Gonzales - FIXED IN BETA
 @if ($foo <= $bar) {
   $baz: 1;
 }
+
+.foo {
+  z-index: -1;
+}
+
+// ========================
+// Interesting user cases
+// ========================
+
+// Should ignore the this
+.foo {
+  $val: -($foo * 2);
+}
+
+// Should flag this if rule is true
+.foo {
+  $val: 1-($foo * 2);
+}
+
+// Should flag this if rule is false
+.foo {
+  $val: 1 - ($foo * 2);
+}
+
+// Invalid Sass so compile would fail - Issue #483
+// .foo {
+//   $val: - ($foo * 2);
+// }
diff --git a/tests/yml/.error-output.yml b/tests/yml/.error-output.yml
new file mode 100644
index 0000000..6f03b59
--- /dev/null
+++ b/tests/yml/.error-output.yml
@@ -0,0 +1,9 @@
+options:
+  formatter: stylish
+  cache-config: false
+  merge-default-rules: false
+files:
+  include: '**/*.s+(a|c)ss'
+rules:
+  no-ids: 2
+  no-color-literals: 1