diff --git a/README.md b/README.md index 1b98f08c6..6d3e7a2bd 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,9 @@ jscs path[ path[...]] --reporter=./some-dir/my-reporter.js ### `--no-colors` Clean output without colors. +### '--max-errors' +Set the maximum number of errors to report + ### `--help` Outputs usage information. @@ -166,6 +169,20 @@ Values: A single file extension or an Array of file extensions, beginning with a "fileExtensions": [".js"] ``` +### maxErrors + +Set the maximum number of errors to report + +Type: `Number` + +Default: 50 + +#### Example + +```js +"maxErrors": 10 +``` + ## Error Suppression ### Inline Comments diff --git a/bin/jscs b/bin/jscs index a217443a1..57ffd1076 100755 --- a/bin/jscs +++ b/bin/jscs @@ -18,6 +18,7 @@ program .option('-p, --preset ', 'preset config') .option('-r, --reporter ', 'error reporter, console - default, text, checkstyle, junit, inline') .option('-v, --verbose', 'adds rule names to the error output') + .option('-m, --max-errors ', 'maximum number of errors to report') .option('', 'Also accepts relative or absolute path to custom reporter') .option('', 'For instance:') .option('', '\t ../some-dir/my-reporter.js\t(relative path with extension)') diff --git a/lib/checker.js b/lib/checker.js index 468ddfa21..3f5dcf0f5 100644 --- a/lib/checker.js +++ b/lib/checker.js @@ -7,6 +7,7 @@ var path = require('path'); var additionalRules = require('./options/additional-rules'); var excludeFiles = require('./options/exclude-files'); var fileExtensions = require('./options/file-extensions'); +var maxErrors = require('./options/max-errors'); /** * Starts Code Style checking process. @@ -30,6 +31,7 @@ Checker.prototype.configure = function(config) { fileExtensions(config, this); excludeFiles(config, this, cwd); additionalRules(config, this, cwd); + maxErrors(config, this); StringChecker.prototype.configure.apply(this, arguments); }; diff --git a/lib/cli.js b/lib/cli.js index daab6f232..07b134dca 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -82,6 +82,10 @@ module.exports = function(program) { config.preset = program.preset; } + if (program.maxErrors) { + config.maxErrors = Number(program.maxErrors); + } + if (program.reporter) { reporterPath = path.resolve(process.cwd(), program.reporter); returnArgs.reporter = reporterPath; @@ -135,6 +139,10 @@ module.exports = function(program) { reporter(errorsCollection); + if (checker.maxErrorsHit) { + console.log('Hi friend, there were more errors.'); + } + errorsCollection.forEach(function(errors) { if (!errors.isEmpty()) { defer.reject(2); diff --git a/lib/errors.js b/lib/errors.js index 41e326f1c..45e4b54a4 100644 --- a/lib/errors.js +++ b/lib/errors.js @@ -14,6 +14,29 @@ var Errors = function(file, verbose) { this._verbose = verbose || false; }; +/** + * Default maximum number of errors + * + * Dangling for tests to reset + * + * @type {Number} + */ +Errors._defaultMaxErrors = 50; + +/** + * Tracks the number of errors found across all instances + * + * @type {Number} + */ +Errors._errorCount = 0; + +/** + * Maximum number of errors possible across all instances + * + * @type {Number} + */ +Errors.maxErrors = Errors._defaultMaxErrors; + Errors.prototype = { /** * Adds style error to the list @@ -21,6 +44,9 @@ Errors.prototype = { * @param {String} message * @param {Number|Object} line * @param {Number} [column] + * @returns {Boolean|undefined} true if the addition was successful, + * false if the add limit was hit + * undefined if the rule is not enabled */ add: function(message, line, column) { if (typeof line === 'object') { @@ -32,12 +58,22 @@ Errors.prototype = { return; } + // We're trying to add more than the max number of errors + if (Errors._errorCount === Errors.maxErrors) { + this.maxErrorsHit = true; + return false; + } + this._errorList.push({ rule: this._currentRule, message: this._verbose ? this._currentRule + ': ' + message : message, line: line, column: column || 0 }); + + Errors._errorCount++; + + return true; }, /** diff --git a/lib/options/max-errors.js b/lib/options/max-errors.js new file mode 100644 index 000000000..c84b62970 --- /dev/null +++ b/lib/options/max-errors.js @@ -0,0 +1,18 @@ +var Errors = require('../errors'); + +/** + * Limits the total number of errors reported + * + * @param {Object} config + * @param {lib/checker} instance + */ +module.exports = function(config, instance) { + if (config.maxErrors) { + Errors.maxErrors = Number(config.maxErrors); + } + + Object.defineProperty(config, 'maxErrors', { + value: config.maxErrors, + enumerable: false + }); +}; diff --git a/lib/string-checker.js b/lib/string-checker.js index af6ceff6a..51d5303dc 100644 --- a/lib/string-checker.js +++ b/lib/string-checker.js @@ -265,6 +265,8 @@ StringChecker.prototype = { }, this); + this.maxErrorsHit = errors.maxErrorsHit; + // sort errors list to show errors as they appear in source errors.getErrorList().sort(function(a, b) { return (a.line - b.line) || (a.column - b.column); diff --git a/test/cli.js b/test/cli.js index 269e34f20..940259c6e 100644 --- a/test/cli.js +++ b/test/cli.js @@ -11,6 +11,7 @@ var exec = require('child_process').exec; var cli = rewire('../lib/cli'); var startingDir = process.cwd(); +var Errors = require('../lib/errors'); describe('modules/cli', function() { before(function() { @@ -524,4 +525,29 @@ describe('modules/cli', function() { }); }); }); + + describe('maxErrors option', function() { + beforeEach(function() { + sinon.spy(console, 'log'); + Errors._resetMaxErrors(); + }); + + afterEach(function() { + console.log.restore(); + Errors._resetMaxErrorsForTests(); + }); + + it('should limit the number of errors reported to the provided amount', function(done) { + return cli({ + maxErrors: 1, + args: ['test/data/cli/error.js'], + config: 'test/data/cli/maxErrors.json' + }) + .promise.always(function() { + assert(console.log.getCall(1).args[0].indexOf('1 code style error found.') !== -1); + rAfter(); + done(); + }); + }); + }); }); diff --git a/test/data/cli/maxErrors.json b/test/data/cli/maxErrors.json new file mode 100644 index 000000000..12fe1d4d9 --- /dev/null +++ b/test/data/cli/maxErrors.json @@ -0,0 +1,5 @@ +{ + "disallowKeywords": ["with"], + "requireSpaceBeforePostfixUnaryOperators": ["++"] +} + diff --git a/test/errors.js b/test/errors.js index 5223bd3a2..dc906937b 100644 --- a/test/errors.js +++ b/test/errors.js @@ -1,5 +1,7 @@ var Checker = require('../lib/checker'); +var Errors = require('../lib/errors'); var assert = require('assert'); +var fs = require('fs'); describe('modules/errors', function() { var checker = new Checker(); @@ -88,4 +90,12 @@ describe('modules/errors', function() { assert.ok(errors.isEmpty()); }); + + it('should report a maximum of 50 errors, by default', function() { + Errors._resetMaxErrors(); + var fixture = fs.readFileSync(__dirname + '/data/validate-indentation.js', 'utf8'); + checker.configure({ validateIndentation: 2 }); + assert(checker.checkString(fixture).getErrorList().length === 50); + Errors._resetMaxErrorsForTests(); + }); }); diff --git a/test/mocha.opts b/test/mocha.opts index 4ebbf346b..c2496d75e 100644 --- a/test/mocha.opts +++ b/test/mocha.opts @@ -1,3 +1,3 @@ -test/*.js test/rules/*.js test/options/*.js test/reporters/*.js +test/test-config.js test/*.js test/rules/*.js test/options/*.js test/reporters/*.js -R dot -u bdd diff --git a/test/rules/validate-indentation.js b/test/rules/validate-indentation.js index 663adec14..2da559f5d 100644 --- a/test/rules/validate-indentation.js +++ b/test/rules/validate-indentation.js @@ -36,7 +36,8 @@ describe('rules/validate-indentation', function() { }); it('should validate 2 spaces indentation properly', function() { - checker.configure({ validateIndentation: 2 }); + // maxErrors set to avoid this test failing with the default value + checker.configure({ validateIndentation: 2, maxErrors: 500 }); checkErrors(fixture, [ 5, 10, diff --git a/test/string-checker.js b/test/string-checker.js index c317344e9..84ac49f34 100644 --- a/test/string-checker.js +++ b/test/string-checker.js @@ -1,4 +1,5 @@ var Checker = require('../lib/checker'); +var Errors = require('../lib/errors'); var assert = require('assert'); describe('modules/string-checker', function() { @@ -70,6 +71,32 @@ describe('modules/string-checker', function() { } }); + describe('maxErrors', function() { + beforeEach(function() { + Errors._resetMaxErrors(); + checker.configure({ + requireSpaceBeforeBinaryOperators: ['='], + maxErrors: 1 + }); + }); + + afterEach(function() { + Errors._resetMaxErrorsForTests(); + }); + + it('should allow a maximum number of reported errors to be set', function() { + var errors = checker.checkString('var foo=1;\n var bar=2;').getErrorList(); + assert(errors.length === 1); + }); + + it('should not report more than the maximum errors across multiple checks', function() { + var errors = checker.checkString('var foo=1;\n var bar=2;').getErrorList(); + var errors2 = checker.checkString('var baz=1;\n var qux=2;').getErrorList(); + assert(errors.length === 1); + assert(errors2.length === 0); + }); + }); + describe('rules registration', function() { it('should report rules in config which don\'t match any registered rules', function() { var error; diff --git a/test/test-config.js b/test/test-config.js new file mode 100644 index 000000000..02cc4f397 --- /dev/null +++ b/test/test-config.js @@ -0,0 +1,24 @@ +var Errors = require('../lib/errors'); + +var largeDefault = 1000000; + +// Set an arbitrarily large number of errors for the tests to bypass +// the maxErrors default of 50 errors. Otherwise, the specs would +// hit the limit and fail the suite. +Errors.maxErrors = largeDefault; + +/** + * Reset maxErrors to the large default value. + * + * Tests for maxError will need to manipulate the constructor's value + * at runtime and need a way to set the testing default + */ +Errors._resetMaxErrorsForTests = function() { + Errors._resetMaxErrors(); + Errors.maxErrors = largeDefault; +}; + +Errors._resetMaxErrors = function() { + this._errorCount = 0; + this.maxErrors = this._defaultMaxErrors; +};