Repository: sasstools/sass-lint (js)

Issue:
CLI outputs invalid JSON when detecting multiple targets
Steps to reproduce:

1. Create a folder `foo`.
2. Create `foo/1.scss` and `foo/2.scss` with `#foo { color: red }`)
3. Run `sass-lint -q -v -f json foo/*`.

What did you expect to happen?

It to print a single array.

What actually happened?

It prints numerous arrays, resulting in invalid json if you try to parse the complete stdout.

Example:

![selection_016](https://cloud.githubusercontent.com/assets/93340/20324533/f85eb070-ab4e-11e6-9253-21547761bc32.png)

Note: I think I found the issue in `bin/sass-lint.js`. I currently have a [commit](https://github.com/brentlintner/sass-lint/commit/e431ad571722f518de8f67dc721647752c528f47) in my fork where I did a quick fix to get things moving for the project I'm working on.

Hope this helps, and thanks for the awesome project.  :-)

Pull request description:
Fix output
Fixes #955 

The output of sass-lint was completely broken in certain instances.. If you output in json format with the cli the resulting json was invalid as a whole, it was valid if you didn't mind piping each file result separately but not as a array of all results. Similarly if you chose to output to a file only the last file to be linted would actually persist to the file as the previous ones would be overwritten.

Im tempted to say we even v1 this one as it technically fixes a long standing bug. Just if users have gotten around said bugs this fix will break things for them...

`<DCO 1.1 Signed-off-by: Dan Purdy dan@dpurdy.me>`


Patch:
diff --git a/bin/sass-lint.js b/bin/sass-lint.js
index c4ee7b5..e785c90 100755
--- a/bin/sass-lint.js
+++ b/bin/sass-lint.js
@@ -16,20 +16,13 @@ var tooManyWarnings = function (detects, userConfig) {
   return warningCount > 0 && warningCount > userConfig.options['max-warnings'];
 };
 
-var detectPattern = function (pattern, userConfig) {
+var detectPattern = function (pattern) {
   var detects = lint.lintFiles(pattern, configOptions, configPath);
 
-  if (program.verbose) {
-    lint.outputResults(detects, configOptions, configPath);
-  }
-
-  if (lint.errorCount(detects).count || tooManyWarnings(detects, userConfig)) {
-    exitCode = 1;
-  }
-
   if (program.exit) {
     lint.failOnError(detects, configOptions, configPath);
   }
+  return detects;
 };
 
 program
@@ -72,17 +65,28 @@ if (program.maxWarnings && program.maxWarnings !== true) {
   configOptions.options['max-warnings'] = program.maxWarnings;
 }
 
-// load our config here so we only load it once for each file
-config = lint.getConfig(configOptions, configPath);
+(function () {
+  // load our config here so we only load it once for each file
+  config = lint.getConfig(configOptions, configPath);
+  var results = [];
 
-if (program.args.length === 0) {
-  detectPattern(null, config);
-}
-else {
-  program.args.forEach(function (path) {
-    detectPattern(path, config);
-  });
-}
+  if (program.args.length === 0) {
+    results = results.concat(detectPattern(null));
+  }
+  else {
+    program.args.forEach(function (path) {
+      results = results.concat(detectPattern(path));
+    });
+  }
+
+  if (program.verbose) {
+    lint.outputResults(results, configOptions, configPath);
+  }
+
+  if (lint.errorCount(results).count || tooManyWarnings(results, config)) {
+    exitCode = 1;
+  }
+}());
 
 process.on('exit', function () {
   process.exit(exitCode); // eslint-disable-line no-process-exit
diff --git a/index.js b/index.js
index f3ff5b5..1fba96d 100644
--- a/index.js
+++ b/index.js
@@ -35,7 +35,7 @@ sassLint.getConfig = function (config, configPath) {
  * Parses our results object to count errors and return
  * paths to files with detected errors.
  *
- * @param {object} results our results object
+ * @param {Array} results our results Array
  * @returns {object} errors object containing the error count and paths for files incl. errors
  */
 sassLint.errorCount = function (results) {
@@ -58,7 +58,7 @@ sassLint.errorCount = function (results) {
  * Parses our results object to count warnings and return
  * paths to files with detected warnings.
  *
- * @param {object} results our results object
+ * @param {Array} results our results array
  * @returns {object} warnings object containing the error count and paths for files incl. warnings
  */
 sassLint.warningCount = function (results) {
@@ -81,7 +81,7 @@ sassLint.warningCount = function (results) {
  * Parses our results object to count warnings and errors and return
  * a cumulative count of both
  *
- * @param {object} results our results object
+ * @param {Array} results our results array
  * @returns {int} the cumulative count of errors and warnings detected
  */
 sassLint.resultCount = function (results) {
@@ -188,7 +188,7 @@ sassLint.lintFileText = function (file, options, configPath) {
  * @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
+ * @returns {Array} results object containing all results
  */
 sassLint.lintFiles = function (files, options, configPath) {
   var that = this,
@@ -245,10 +245,10 @@ sassLint.lintFiles = function (files, options, configPath) {
 /**
  * Handles formatting of results using EsLint formatters
  *
- * @param {object} results our results object
+ * @param {Array} results our results array
  * @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
+ * @returns {string} results our results object in the user specified format
  */
 sassLint.format = function (results, options, configPath) {
   var config = this.getConfig(options, configPath),
@@ -263,10 +263,10 @@ sassLint.format = function (results, options, configPath) {
  * 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 {Array} results our results array
  * @param {object} options user specified rules/options passed in
  * @param {string} configPath path to a config file
- * @returns {object} results our results object
+ * @returns {string} the formatted results string
  */
 sassLint.outputResults = function (results, options, configPath) {
   var config = this.getConfig(options, configPath);
@@ -295,7 +295,7 @@ 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 {Array} results - our results array
  * @param {object} [options] - extra options to use when running failOnError, e.g. max-warnings
  * @param {string} [configPath] - path to the config file
  * @returns {void}


Test patch:
diff --git a/tests/cli.js b/tests/cli.js
index df7c7fe..3bfbbdf 100644
--- a/tests/cli.js
+++ b/tests/cli.js
@@ -150,6 +150,29 @@ describe('cli', function () {
     });
   });
 
+  it('CLI format option should output valid JSON', function (done) {
+    var command = 'node bin/sass-lint -c tests/yml/.stylish-output.yml tests/cli/*.scss --verbose --format json';
+
+    exec(command, function (err, stdout) {
+
+      if (err) {
+        return done(err);
+      }
+      else {
+        try {
+          var result = JSON.parse(stdout);
+          if (result && result.length && result.length > 1) {
+            return done();
+          }
+          return done(new Error('Output is not combined'));
+        }
+        catch (e) {
+          return done(new Error('Not JSON'));
+        }
+      }
+    });
+  });
+
   it('CLI output option should write to test file', function (done) {
     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');
diff --git a/tests/cli/cli-warn.scss b/tests/cli/cli-warn.scss
new file mode 100644
index 0000000..b8075d6
--- /dev/null
+++ b/tests/cli/cli-warn.scss
@@ -0,0 +1,3 @@
+.cli {
+  color: red;
+}