From 2908a9140973dc7d95eb9e6a01ef0d583efcc0e1 Mon Sep 17 00:00:00 2001 From: Joel Kemp Date: Wed, 29 Oct 2014 23:54:52 -0400 Subject: [PATCH] Errors: Support a filter to control which errors are reported Closes gh-728 Fixes #335 --- README.md | 44 ++++++++++++++++++++++++++++++++++++ bin/jscs | 1 + lib/cli.js | 4 ++++ lib/config/configuration.js | 20 +++++++++++++++- lib/errors.js | 15 +++++++++--- lib/string-checker.js | 2 +- test/cli.js | 21 +++++++++++++++++ test/config/configuration.js | 8 +++++++ test/data/error-filter.js | 4 ++++ test/errors.js | 41 ++++++++++++++++++++++++++++++--- 10 files changed, 152 insertions(+), 8 deletions(-) create mode 100644 test/data/error-filter.js diff --git a/README.md b/README.md index 7b0ce4d65..c99d507bf 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,44 @@ 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` or `Function` + +#### Example + +```js +"errorFilter": "path/to/my/filter.js" +``` + +You may also supply a function with that accepts an error object +and returns a boolean indicating whether or not to exclude an error: + +```js +"errorFilter": function(error) { + // Analyze the error object... + + // Return false to exclude this error + // Return true to report this error normally + return false; +} +``` + +The `error` is of the following format: + +```js +{ + filename: '', + rule: '', + message: '', + line: 0, + column: 0 +} +``` + ## Error Suppression ### Inline Comments diff --git a/bin/jscs b/bin/jscs index 71e490b7f..974a55f77 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 [path]', '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..e15e54597 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -88,6 +88,10 @@ module.exports = function(program) { checker.getConfiguration().override({maxErrors: Number(program.maxErrors)}); } + if (program.errorFilter) { + checker.getConfiguration().override({errorFilter: program.errorFilter}); + } + if (program.reporter) { reporterPath = path.resolve(process.cwd(), program.reporter); returnArgs.reporter = reporterPath; diff --git a/lib/config/configuration.js b/lib/config/configuration.js index 53810b790..313c62584 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 }; /** @@ -32,6 +33,7 @@ function Configuration() { this._overrides = {}; this._presetName = null; this._esnextEnabled = false; + this._errorFilter = null; } /** @@ -88,6 +90,7 @@ Configuration.prototype.getProcessedConfig = function() { result.maxErrors = this._maxErrors; result.preset = this._presetName; result.esnext = this._esnextEnabled; + result.errorFilter = this._errorFilter; return result; }; @@ -149,6 +152,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. * @@ -250,6 +262,12 @@ Configuration.prototype._processConfig = function(config) { this._esnextEnabled = Boolean(config.esnext); } + if (typeof config.errorFilter === 'string') { + this._errorFilter = require(config.errorFilter.replace('.js', '')); + } else if (typeof config.errorFilter === 'function') { + this._errorFilter = config.errorFilter; + } + // Apply config options Object.keys(config).forEach(function(key) { if (!BUILTIN_OPTIONS[key]) { diff --git a/lib/errors.js b/lib/errors.js index c7784b00e..ac625a13e 100644 --- a/lib/errors.js +++ b/lib/errors.js @@ -7,12 +7,14 @@ var colors = require('colors'); * @name Errors * @param {JsFile} file * @param {Boolean} verbose + * @param {Function} errorFilter */ -var Errors = function(file, verbose) { +var Errors = function(file, verbose, errorFilter) { this._errorList = []; this._file = file; this._currentRule = ''; this._verbose = verbose || false; + this._errorFilter = errorFilter; }; Errors.prototype = { @@ -24,6 +26,8 @@ Errors.prototype = { * @param {Number} [column] optional if line is an object */ add: function(message, line, column) { + var error; + if (typeof line === 'object') { column = line.column; line = line.line; @@ -39,12 +43,17 @@ Errors.prototype = { return; } - this._errorList.push({ + error = { + filename: this._file.getFilename(), rule: this._currentRule, message: this._verbose ? this._currentRule + ': ' + message : message, line: line, column: column - }); + }; + + if (!this._errorFilter || this._errorFilter(error)) { + this._errorList.push(error); + } }, /** diff --git a/lib/string-checker.js b/lib/string-checker.js index 74ee712c7..503f7e7c8 100644 --- a/lib/string-checker.js +++ b/lib/string-checker.js @@ -92,7 +92,7 @@ StringChecker.prototype = { parseError = e; } var file = new JsFile(filename, str, tree); - var errors = new Errors(file, this._verbose); + var errors = new Errors(file, this._verbose, this._configuration.getErrorFilter()); if (!this._maxErrorsExceeded) { if (parseError) { 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 54225f570..1a69b266e 100644 --- a/test/config/configuration.js +++ b/test/config/configuration.js @@ -111,6 +111,14 @@ describe('modules/config/configuration', function() { }); }); + describe('getErrorFilter', function() { + it('should return a list of the names of unsupported rules found', function() { + var errorFilter = function() {}; + configuration.load({errorFilter: errorFilter}); + assert(configuration.getErrorFilter() === errorFilter); + }); + }); + describe('hasPreset', function() { it('should return true if preset presents in collection', function() { var preset = {maxErrors: 5}; 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..7b0599cc3 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,35 @@ describe('modules/errors', function() { assert.equal(error.column, 0); }); }); + + describe('filter', function() { + beforeEach(function() { + checker = new Checker(); + checker.registerDefaultRules(); + }); + + it('should accept a path to a filter module 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 accept a function as a filter', function() { + checker.configure({ + disallowQuotedKeysInObjects: true, + errorFilter: function() { + // Accept all errors + return true; + } + }); + + var errors = checker.checkString('var x = { "a": 1 }'); + assert.ok(errors.getErrorCount() === 1); + }); + }); });