diff --git a/README.md b/README.md index e5768e06e..8a890e3c2 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,12 @@ jscs path[ path[...]] --reporter=./some-dir/my-reporter.js ### `--esnext` Attempts to parse your code as ES6 using the harmony version of the esprima parser. Please note that this is currently experimental, and will improve over time. +### `--error-filter` +The path to a module that determines whether or not an error should be reported. +``` +jscs path[ path[...]] --error-filter=path/to/my/module.js +``` + ### `--no-colors` Clean output without colors. @@ -216,6 +222,21 @@ Value: `true` "esnext": true ``` +### errorFilter + +A filter function that determines whether or not to report an error. +This will be called for every found error. + +Type: `String` + +#### Example + +```js +"errorFilter": "path/to/my/filter.js" +``` + +See [how to define an error filter](https://github.com/jscs-dev/node-jscs/wiki/Error-Filters). + ## Error Suppression ### Inline Comments diff --git a/bin/jscs b/bin/jscs index 71e490b7f..7084a0734 100755 --- a/bin/jscs +++ b/bin/jscs @@ -19,6 +19,7 @@ program .option('-p, --preset ', 'preset config') .option('-v, --verbose', 'adds rule names to the error output') .option('-m, --max-errors ', 'maximum number of errors to report') + .option('-f, --error-filter ', 'a module to filter errors') .option('-r, --reporter ', 'error reporter, console - default, text, checkstyle, junit, inline') .option('', 'Also accepts relative or absolute path to custom reporter') .option('', 'For instance:') diff --git a/lib/cli.js b/lib/cli.js index 3e4575f9c..520ff1085 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -24,7 +24,8 @@ module.exports = function(program) { var promise = defer.promise(); var checker = new Checker({ verbose: program.verbose, - esnext: program.esnext + esnext: program.esnext, + errorFilter: program.errorFilter }); var args = program.args; var returnArgs = { diff --git a/lib/config/configuration.js b/lib/config/configuration.js index 9e988d4d4..16ac4284a 100644 --- a/lib/config/configuration.js +++ b/lib/config/configuration.js @@ -10,7 +10,8 @@ var BUILTIN_OPTIONS = { fileExtensions: true, maxErrors: true, configPath: true, - esnext: true + esnext: true, + errorFilter: true }; /** @@ -33,6 +34,7 @@ function Configuration() { this._overrides = {}; this._presetName = null; this._esnextEnabled = false; + this._errorFilter = null; } /** @@ -87,6 +89,7 @@ Configuration.prototype.getProcessedConfig = function() { result.maxErrors = this._maxErrors; result.preset = this._presetName; result.esnext = this._esnextEnabled; + result.errorFilter = this._errorFilter; return result; }; @@ -157,6 +160,15 @@ Configuration.prototype.isESNextEnabled = function() { return this._esnextEnabled; }; +/** + * Returns the loaded error filter. + * + * @returns {Function|null} + */ +Configuration.prototype.getErrorFilter = function() { + return this._errorFilter; +}; + /** * Returns base path. * @@ -258,6 +270,10 @@ Configuration.prototype._processConfig = function(config) { this._esnextEnabled = Boolean(config.esnext); } + if (config.errorFilter) { + this._loadErrorFilter(config.errorFilter); + } + // Apply config options Object.keys(config).forEach(function(key) { if (!BUILTIN_OPTIONS[key]) { @@ -279,6 +295,21 @@ Configuration.prototype._loadPlugin = function(plugin) { plugin(this); }; +/** + * Loads an error filter. + * + * @param {Function|null} errorFilter + * @protected + */ +Configuration.prototype._loadErrorFilter = function(errorFilter) { + assert( + typeof errorFilter === 'function' || + errorFilter === null, + '`errorFilter` option requires a function or null value' + ); + this._errorFilter = errorFilter; +}; + /** * Includes plugin in the configuration environment. * diff --git a/lib/config/node-configuration.js b/lib/config/node-configuration.js index f99554210..2b140c663 100644 --- a/lib/config/node-configuration.js +++ b/lib/config/node-configuration.js @@ -2,6 +2,7 @@ var path = require('path'); var util = require('util'); var glob = require('glob'); var Configuration = require('./configuration'); +var assert = require('assert'); /** * nodejs-compatible configuration module. @@ -34,6 +35,22 @@ NodeConfiguration.prototype._loadPlugin = function(plugin) { Configuration.prototype._loadPlugin.call(this, plugin); }; +/** + * Loads an error filter module + * + * @param {String|null} errorFilter + * @protected + */ +NodeConfiguration.prototype._loadErrorFilter = function(errorFilter) { + assert( + typeof errorFilter === 'string', + '`errorFilter` option requires a string or null value' + ); + + errorFilter = require(errorFilter.replace('.js', '')); + Configuration.prototype._loadErrorFilter.call(this, errorFilter); +}; + /** * Loads additional rule. * diff --git a/lib/errors.js b/lib/errors.js index c7784b00e..dbeb92670 100644 --- a/lib/errors.js +++ b/lib/errors.js @@ -40,6 +40,7 @@ Errors.prototype = { } this._errorList.push({ + filename: this._file.getFilename(), rule: this._currentRule, message: this._verbose ? this._currentRule + ': ' + message : message, line: line, @@ -92,6 +93,15 @@ Errors.prototype = { this._errorList.splice(length); }, + /** + * Filters out errors based on the supplied filter function + * + * @param {Function} filter + */ + filterErrorList: function(filter) { + this._errorList = this._errorList.filter(filter); + }, + /** * Formats error for further output. * diff --git a/lib/string-checker.js b/lib/string-checker.js index 2a9dad650..7038306bb 100644 --- a/lib/string-checker.js +++ b/lib/string-checker.js @@ -93,6 +93,7 @@ StringChecker.prototype = { } var file = new JsFile(filename, str, tree); var errors = new Errors(file, this._verbose); + var errorFilter = this._configuration.getErrorFilter(); if (!this._maxErrorsExceeded) { if (parseError) { @@ -116,6 +117,10 @@ StringChecker.prototype = { return (a.line - b.line) || (a.column - b.column); }); + if (errorFilter) { + errors.filterErrorList(errorFilter); + } + if (this._maxErrors !== null && !isNaN(this._maxErrors)) { if (!this._maxErrorsExceeded) { this._maxErrorsExceeded = this._errorsFound + errors.getErrorCount() > this._maxErrors; diff --git a/test/cli.js b/test/cli.js index be4547dad..6e3d3b7f0 100644 --- a/test/cli.js +++ b/test/cli.js @@ -567,4 +567,25 @@ describe('modules/cli', function() { }); }); }); + + describe('errorFilter option', function() { + beforeEach(function() { + sinon.spy(console, 'log'); + }); + + afterEach(function() { + console.log.restore(); + }); + + it('should accept a path to a filter module', function() { + return cli({ + errorFilter: __dirname + '/data/error-filter.js', + args: ['test/data/cli/error.js'] + }) + .promise.always(function() { + assert(console.log.getCall(0).args[0] === 'No code style errors found.'); + rAfter(); + }); + }); + }); }); diff --git a/test/config/configuration.js b/test/config/configuration.js index 1fc4a1ee5..e81d30911 100644 --- a/test/config/configuration.js +++ b/test/config/configuration.js @@ -118,6 +118,13 @@ describe('modules/config/configuration', function() { }); }); + describe('getErrorFilter', function() { + it('should return the supplied error filter', function() { + configuration.load({errorFilter: function() {}}); + assert(typeof configuration.getErrorFilter() === 'function'); + }); + }); + describe('hasPreset', function() { it('should return true if preset presents in collection', function() { var preset = {maxErrors: 5}; diff --git a/test/config/node-configuration.js b/test/config/node-configuration.js index ba96b85b3..1cd671261 100644 --- a/test/config/node-configuration.js +++ b/test/config/node-configuration.js @@ -76,5 +76,13 @@ describe('modules/config/node-configuration', function() { assert(examplePluginSpy.getCall(0).args[0] === configuration); examplePluginSpy.reset(); }); + + it('should accept `errorFilter` to register an error filter', function() { + configuration.load({ + errorFilter: path.resolve(__dirname, '../data/error-filter.js') + }); + + assert(typeof configuration.getErrorFilter() === 'function'); + }); }); }); diff --git a/test/data/error-filter.js b/test/data/error-filter.js new file mode 100644 index 000000000..d0b9fb4a8 --- /dev/null +++ b/test/data/error-filter.js @@ -0,0 +1,4 @@ +module.exports = function(error) { + // Blocks all errors from being added + return false; +}; diff --git a/test/errors.js b/test/errors.js index 07dac4705..baf356eee 100644 --- a/test/errors.js +++ b/test/errors.js @@ -2,10 +2,14 @@ var Checker = require('../lib/checker'); var assert = require('assert'); describe('modules/errors', function() { - var checker = new Checker(); + var checker; - checker.registerDefaultRules(); - checker.configure({ disallowQuotedKeysInObjects: true }); + beforeEach(function() { + checker = new Checker(); + + checker.registerDefaultRules(); + checker.configure({ disallowQuotedKeysInObjects: true }); + }); it('should provide correct indent for tabbed lines', function() { var errors = checker.checkString('\tvar x = { "a": 1 }'); @@ -118,4 +122,14 @@ describe('modules/errors', function() { assert.equal(error.column, 0); }); }); + + describe('filterErrorList', function() { + it('filters the errorlist by the given function', function() { + var errors = checker.checkString('var'); + errors.filterErrorList(function(error) { + return false; + }); + assert(errors.isEmpty()); + }); + }); }); diff --git a/test/string-checker.js b/test/string-checker.js index 1124d21e8..994a4f612 100644 --- a/test/string-checker.js +++ b/test/string-checker.js @@ -199,4 +199,31 @@ describe('modules/string-checker', function() { assert(error.message !== customDescription); }); }); + + describe('error filter', function() { + beforeEach(function() { + checker = new Checker(); + checker.registerDefaultRules(); + }); + + it('should accept a path to a filter function to filter out errors', function() { + checker.configure({ + disallowQuotedKeysInObjects: true, + errorFilter: __dirname + '/data/error-filter.js' + }); + + var errors = checker.checkString('var x = { "a": 1 }'); + + assert.ok(errors.isEmpty()); + }); + + it('should not accept a filter function directly in the configuration', function() { + assert.throws(function() { + checker.configure({ + disallowQuotedKeysInObjects: true, + errorFilter: function() { return false; } + }); + }); + }); + }); });