Repository: lesshint/lesshint (js)

Issue:
Allow reporters to be objects, not just file paths
When running `lesshint` via `requires`/`imports` in code, instead of the CLI, it would be useful to pass a reporter object instead of a file path to one. This would allow for stateful reporters in tools that require `lesshint`.

The actual change would be pretty small, too - in `Lesshint.prototype.getReporter`, something like: 

```javascript
    // Object with .report as a method: return it directly
    if (reporter.constructor !== 'string' && 'report' in reporter) {
        return reporter;
    }
```

Required for https://github.com/lesshint/lesshint/issues/278.

Pull request description:
Allow reporters to be objects, not just file paths
Fixes #282.

I held off from documenting in this PR since until #283 is resolved I don't believe it would be easy for users to use this.

Patch:
diff --git a/lib/lesshint.js b/lib/lesshint.js
index a866229..255ac72 100644
--- a/lib/lesshint.js
+++ b/lib/lesshint.js
@@ -127,6 +127,11 @@ Lesshint.prototype.getReporter = function (reporter) {
         return require(reporterPath);
     }
 
+    // Object with .report as a method: return it directly
+    if (reporter.constructor !== 'string' && reporter.report) {
+        return reporter;
+    }
+
     // Try to find it somewhere on disk
     try {
         reporterPath = path.resolve(process.cwd(), reporter);
diff --git a/lib/reporters/json.js b/lib/reporters/json.js
new file mode 100644
index 0000000..2c3ac45
--- /dev/null
+++ b/lib/reporters/json.js
@@ -0,0 +1,10 @@
+/*eslint no-console: 0*/
+
+'use strict';
+
+module.exports = {
+    name: 'json',
+    report: function report (results) {
+        console.log(JSON.stringify(results));
+    }
+};


Test patch:
diff --git a/test/specs/cli.js b/test/specs/cli.js
index 9e78a8d..79c74a5 100644
--- a/test/specs/cli.js
+++ b/test/specs/cli.js
@@ -331,6 +331,22 @@ describe('cli', function () {
         });
     });
 
+    it('should exit without errors when passed a reporter object', function () {
+        var result;
+
+        result = cli({
+            args: [path.dirname(__dirname) + '/data/files/ok.less'],
+            reporter: {
+                name: 'test',
+                report: function () {}
+            }
+        });
+
+        return result.then(function (status) {
+            expect(status).to.equal(0);
+        });
+    });
+
     it('should exit with error when passed a invalid reporter name', function () {
         var result;
 
diff --git a/test/specs/lesshint.js b/test/specs/lesshint.js
index da92dc1..9f9a1b6 100644
--- a/test/specs/lesshint.js
+++ b/test/specs/lesshint.js
@@ -185,13 +185,23 @@ describe('lesshint', function () {
     });
 
     describe('getReporter', function () {
-        it('should load the specified reporter', function () {
+        it('should load the specified reporter by path', function () {
             var lesshint = new Lesshint();
             var reporter = lesshint.getReporter(path.resolve(process.cwd() + '/lib/reporters/default.js'));
 
             expect(reporter.name).to.equal('default');
         });
 
+        it('should load the specified reporter directly', function () {
+            var lesshint = new Lesshint();
+            var reporter = lesshint.getReporter({
+                name: 'test',
+                report: function () {}
+            });
+
+            expect(reporter.name).to.equal('test');
+        });
+
         it('should load the default when nothing is passed', function () {
             var lesshint = new Lesshint();
             var reporter = lesshint.getReporter();
diff --git a/test/specs/reporters/json.js b/test/specs/reporters/json.js
new file mode 100644
index 0000000..732b3ae
--- /dev/null
+++ b/test/specs/reporters/json.js
@@ -0,0 +1,43 @@
+/*eslint no-console: 0*/
+
+'use strict';
+
+var expect = require('chai').expect;
+var rewire = require('rewire');
+var sinon = require('sinon');
+
+describe('reporter:json', function () {
+    var reporter = rewire('../../../lib/reporters/json.js');
+
+    beforeEach(function () {
+        sinon.stub(process.stdout, 'write');
+    });
+
+    afterEach(function () {
+        if (process.stdout.write.restore) {
+            process.stdout.write.restore();
+        }
+    });
+
+    it('should print an error completely', function () {
+        var message;
+        var errors = [{
+            line: 1,
+            file: 'file.less',
+            linter: 'spaceBeforeBrace',
+            message: 'Opening curly brace should be preceded by one space.',
+            severity: 'error',
+            source: '.foo{ color: red; }'
+        }];
+
+        sinon.spy(console, 'log');
+
+        reporter.report(errors);
+
+        message = console.log.getCall(0).args[0];
+
+        expect(message).to.equal(JSON.stringify(errors));
+
+        console.log.restore();
+    });
+});