diff --git a/lib/checker.js b/lib/checker.js index b32f38ca2..63b4f4119 100644 --- a/lib/checker.js +++ b/lib/checker.js @@ -3,10 +3,9 @@ var Vow = require('vow'); var StringChecker = require('./string-checker'); var utils = require('util'); var path = require('path'); +var minimatch = require('minimatch'); -var additionalRules = require('./options/additional-rules'); -var excludeFiles = require('./options/exclude-files'); -var fileExtensions = require('./options/file-extensions'); +var NodeConfiguration = require('./config/node-configuration'); /** * Starts Code Style checking process. @@ -27,11 +26,15 @@ utils.inherits(Checker, StringChecker); Checker.prototype.configure = function(config) { var cwd = config.configPath ? path.dirname(config.configPath) : process.cwd(); - fileExtensions(config, this); - excludeFiles(config, this, cwd); - additionalRules(config, this, cwd); - StringChecker.prototype.configure.apply(this, arguments); + + this._fileExtensions = this._configuration.getFileExtensions(); + + this._excludes = this._configuration.getExcludedFiles().map(function(pattern) { + return new minimatch.Minimatch(path.resolve(cwd, pattern), { + dot: true + }); + }); }; /** @@ -41,12 +44,10 @@ Checker.prototype.configure = function(config) { * @returns {Promise * Errors} */ Checker.prototype.checkFile = function(path) { - var _this = this; - - if (!_this._isExcluded(path)) { + if (!this._isExcluded(path)) { return vowFs.read(path, 'utf8').then(function(data) { - return _this.checkString(data, path); - }); + return this.checkString(data, path); + }, this); } var defer = Vow.defer(); @@ -62,38 +63,36 @@ Checker.prototype.checkFile = function(path) { * @returns {Promise * Error[]} */ Checker.prototype.checkDirectory = function(path) { - var _this = this; - return vowFs.listDir(path).then(function(filenames) { return Vow.all(filenames.map(function(filename) { var fullname = path + '/' + filename; // check for exclude path - if (_this._isExcluded(fullname)) { + if (this._isExcluded(fullname)) { return []; } return vowFs.stat(fullname).then(function(stat) { if (stat.isDirectory()) { - return _this.checkDirectory(fullname); + return this.checkDirectory(fullname); } - if (!_this._hasCorrectExtension(fullname)) { + if (!this._hasCorrectExtension(fullname)) { return []; } - return Vow.when(_this.checkFile(fullname)).then(function(errors) { + return Vow.when(this.checkFile(fullname)).then(function(errors) { if (errors) { return errors; } return []; }); - }); - })).then(function(results) { + }, this); + }, this)).then(function(results) { return [].concat.apply([], results); }); - }); + }, this); }; /** @@ -181,4 +180,14 @@ Checker.prototype._hasCorrectExtension = function(testPath) { ); }; +/** + * Returns new configuration instance. + * + * @protected + * @returns {Configuration} + */ +Checker.prototype._createConfiguration = function() { + return new NodeConfiguration(); +}; + module.exports = Checker; diff --git a/lib/config/configuration.js b/lib/config/configuration.js new file mode 100644 index 000000000..7775d5aef --- /dev/null +++ b/lib/config/configuration.js @@ -0,0 +1,362 @@ +var assert = require('assert'); + +var BUILTIN_OPTIONS = { + plugins: true, + preset: true, + excludeFiles: true, + additionalRules: true, + fileExtensions: true, + maxErrors: true +}; + +/** + * JSCS Configuration. + * Browser/Rhino-compatible. + * + * @name Configuration + */ +function Configuration() { + this._presets = {}; + this._rules = {}; + this._configuredRules = []; + this._fileExtensions = ['.js']; + this._excludedFiles = []; + this._ruleSettings = {}; + this._maxErrors = null; +} + +/** + * Load settings from a configuration. + * + * @param {Object} config + */ +Configuration.prototype.load = function(config) { + var ruleSettings = this._processConfig(config); + var unsupportedRules = []; + Object.keys(ruleSettings).forEach(function(optionName) { + var rule = this._rules[optionName]; + if (rule) { + var optionValue = ruleSettings[optionName]; + if (optionValue !== null) { + rule.configure(ruleSettings[optionName]); + this._configuredRules.push(rule); + } + } else { + unsupportedRules.push(optionName); + } + }, this); + if (unsupportedRules.length > 0) { + throw new Error('Unsupported rules: ' + unsupportedRules.join(', ')); + } + this._ruleSettings = ruleSettings; +}; + +Configuration.prototype.getProcessedConfig = function() { + var result = {}; + Object.keys(this._ruleSettings).forEach(function(key) { + result[key] = this._ruleSettings[key]; + }, this); + result.excludeFiles = this._excludedFiles; + result.fileExtensions = this._fileExtensions; + result.maxErrors = this._maxErrors; + return result; +}; + +/** + * Returns list of configured rules. + * + * @returns {Rule[]} + */ +Configuration.prototype.getConfiguredRules = function() { + return this._configuredRules; +}; + +/** + * Returns excluded file mask list. + * + * @returns {String[]} + */ +Configuration.prototype.getExcludedFiles = function() { + return this._excludedFiles; +}; + +/** + * Returns file extension list. + * + * @returns {String[]} + */ +Configuration.prototype.getFileExtensions = function() { + return this._fileExtensions; +}; + +/** + * Returns maximal error count. + * + * @returns {Number|undefined} + */ +Configuration.prototype.getMaxErrors = function() { + return this._maxErrors; +}; + +/** + * Processes configuration and returns config options. + * + * @param {Object} config + * @returns {Object} + */ +Configuration.prototype._processConfig = function(config) { + var ruleSettings = {}; + + // Load plugins + if (config.plugins) { + assert(Array.isArray(config.plugins), '`plugins` option requires array value'); + config.plugins.forEach(this._loadPlugin, this); + } + + // Apply presets + if (config.preset) { + var presetName = config.preset; + assert(typeof presetName === 'string', '`preset` option requires string value'); + var presetData = this._presets[presetName]; + assert(Boolean(presetData), 'Preset "' + presetName + '" does not exist'); + var presetResult = this._processConfig(presetData); + Object.keys(presetResult).forEach(function(key) { + if (!BUILTIN_OPTIONS[key]) { + ruleSettings[key] = presetResult[key]; + } + }); + } + + // File extensions + if (config.fileExtensions) { + assert( + typeof config.fileExtensions === 'string' || Array.isArray(config.fileExtensions), + '`fileExtensions` option requires string or array value' + ); + this._fileExtensions = [].concat(config.fileExtensions).map(function(ext) { + return ext.toLowerCase(); + }); + } + + // File excludes + if (config.excludeFiles) { + assert(Array.isArray(config.excludeFiles), '`excludeFiles` option requires array value'); + this._excludedFiles = config.excludeFiles; + } + + // Additional rules + if (config.additionalRules) { + assert(Array.isArray(config.additionalRules), '`additionalRules` option requires array value'); + config.additionalRules.plugins.forEach(this._loadAdditionalRule, this); + } + + if (config.hasOwnProperty('maxErrors')) { + assert( + typeof config.maxErrors === 'number' || config.maxErrors === null, + '`maxErrors` option requires number or null value' + ); + this._maxErrors = config.maxErrors; + } + + // Apply config options + Object.keys(config).forEach(function(key) { + if (!BUILTIN_OPTIONS[key]) { + ruleSettings[key] = config[key]; + } + }); + + return ruleSettings; +}; + +/** + * Loads plugin data. + * + * @param {function(Configuration)} plugin + * @protected + */ +Configuration.prototype._loadPlugin = function(plugin) { + assert(typeof plugin === 'function', '`plugin` should be a function'); + plugin(this); +}; + +/** + * Loads additional rule. + * + * @param {Rule} additionalRule + * @protected + */ +Configuration.prototype._loadAdditionalRule = function(additionalRule) { + assert(typeof additionalRule === 'object', '`additionalRule` should be an object'); + this.registerRule(additionalRule); +}; + +/** + * Adds rule to the collection. + * + * @param {Rule} rule + */ +Configuration.prototype.registerRule = function(rule) { + this._rules[rule.getOptionName()] = rule; +}; + +/** + * Adds preset to the collection. + * + * @param {String} presetName + * @param {Object} presetConfig + */ +Configuration.prototype.registerPreset = function(presetName, presetConfig) { + this._presets[presetName] = presetConfig; +}; + +/** + * Registers built-in Code Style cheking rules. + */ +Configuration.prototype.registerDefaultRules = function() { + + /* + Important! + These rules are linked explicitly to keep browser-version supported. + */ + + this.registerRule(new (require('../rules/require-curly-braces'))()); + this.registerRule(new (require('../rules/require-multiple-var-decl'))()); + this.registerRule(new (require('../rules/disallow-multiple-var-decl'))()); + this.registerRule(new (require('../rules/disallow-empty-blocks'))()); + this.registerRule(new (require('../rules/require-space-after-keywords'))()); + this.registerRule(new (require('../rules/disallow-space-after-keywords'))()); + this.registerRule(new (require('../rules/require-parentheses-around-iife'))()); + + /* deprecated rules */ + this.registerRule(new (require('../rules/require-left-sticked-operators'))()); + this.registerRule(new (require('../rules/disallow-left-sticked-operators'))()); + this.registerRule(new (require('../rules/require-right-sticked-operators'))()); + this.registerRule(new (require('../rules/disallow-right-sticked-operators'))()); + this.registerRule(new (require('../rules/validate-jsdoc'))()); + /* deprecated rules (end) */ + + this.registerRule(new (require('../rules/require-operator-before-line-break'))()); + this.registerRule(new (require('../rules/disallow-implicit-type-conversion'))()); + this.registerRule(new (require('../rules/require-camelcase-or-uppercase-identifiers'))()); + this.registerRule(new (require('../rules/disallow-keywords'))()); + this.registerRule(new (require('../rules/disallow-multiple-line-breaks'))()); + this.registerRule(new (require('../rules/disallow-multiple-line-strings'))()); + this.registerRule(new (require('../rules/validate-line-breaks'))()); + this.registerRule(new (require('../rules/validate-quote-marks'))()); + this.registerRule(new (require('../rules/validate-indentation'))()); + this.registerRule(new (require('../rules/disallow-trailing-whitespace'))()); + this.registerRule(new (require('../rules/disallow-mixed-spaces-and-tabs'))()); + this.registerRule(new (require('../rules/require-keywords-on-new-line'))()); + this.registerRule(new (require('../rules/disallow-keywords-on-new-line'))()); + this.registerRule(new (require('../rules/require-line-feed-at-file-end'))()); + this.registerRule(new (require('../rules/maximum-line-length'))()); + this.registerRule(new (require('../rules/require-yoda-conditions'))()); + this.registerRule(new (require('../rules/disallow-yoda-conditions'))()); + this.registerRule(new (require('../rules/require-spaces-inside-object-brackets'))()); + this.registerRule(new (require('../rules/require-spaces-inside-array-brackets'))()); + this.registerRule(new (require('../rules/require-spaces-inside-parentheses'))()); + this.registerRule(new (require('../rules/disallow-spaces-inside-object-brackets'))()); + this.registerRule(new (require('../rules/disallow-spaces-inside-array-brackets'))()); + this.registerRule(new (require('../rules/disallow-spaces-inside-parentheses'))()); + this.registerRule(new (require('../rules/require-blocks-on-newline'))()); + this.registerRule(new (require('../rules/require-space-after-object-keys'))()); + this.registerRule(new (require('../rules/require-space-before-object-values'))()); + this.registerRule(new (require('../rules/disallow-space-after-object-keys'))()); + this.registerRule(new (require('../rules/disallow-space-before-object-values'))()); + this.registerRule(new (require('../rules/disallow-quoted-keys-in-objects'))()); + this.registerRule(new (require('../rules/disallow-dangling-underscores'))()); + this.registerRule(new (require('../rules/require-aligned-object-values'))()); + + this.registerRule(new (require('../rules/disallow-padding-newlines-in-blocks'))()); + this.registerRule(new (require('../rules/require-padding-newlines-in-blocks'))()); + this.registerRule(new (require('../rules/require-padding-newlines-in-objects'))()); + this.registerRule(new (require('../rules/disallow-padding-newlines-in-objects'))()); + this.registerRule(new (require('../rules/require-newline-before-block-statements'))()); + this.registerRule(new (require('../rules/disallow-newline-before-block-statements'))()); + + this.registerRule(new (require('../rules/disallow-trailing-comma'))()); + this.registerRule(new (require('../rules/require-trailing-comma'))()); + + this.registerRule(new (require('../rules/disallow-comma-before-line-break'))()); + this.registerRule(new (require('../rules/require-comma-before-line-break'))()); + + this.registerRule(new (require('../rules/disallow-space-before-block-statements.js'))()); + this.registerRule(new (require('../rules/require-space-before-block-statements.js'))()); + + this.registerRule(new (require('../rules/disallow-space-before-postfix-unary-operators.js'))()); + this.registerRule(new (require('../rules/require-space-before-postfix-unary-operators.js'))()); + + this.registerRule(new (require('../rules/disallow-space-after-prefix-unary-operators.js'))()); + this.registerRule(new (require('../rules/require-space-after-prefix-unary-operators.js'))()); + + this.registerRule(new (require('../rules/disallow-space-before-binary-operators'))()); + this.registerRule(new (require('../rules/require-space-before-binary-operators'))()); + + this.registerRule(new (require('../rules/disallow-space-after-binary-operators'))()); + this.registerRule(new (require('../rules/require-space-after-binary-operators'))()); + + this.registerRule(new (require('../rules/require-spaces-in-conditional-expression'))()); + this.registerRule(new (require('../rules/disallow-spaces-in-conditional-expression'))()); + + this.registerRule(new (require('../rules/require-spaces-in-function'))()); + this.registerRule(new (require('../rules/disallow-spaces-in-function'))()); + this.registerRule(new (require('../rules/require-spaces-in-function-expression'))()); + this.registerRule(new (require('../rules/disallow-spaces-in-function-expression'))()); + this.registerRule(new (require('../rules/require-spaces-in-anonymous-function-expression'))()); + this.registerRule(new (require('../rules/disallow-spaces-in-anonymous-function-expression'))()); + this.registerRule(new (require('../rules/require-spaces-in-named-function-expression'))()); + this.registerRule(new (require('../rules/disallow-spaces-in-named-function-expression'))()); + this.registerRule(new (require('../rules/require-spaces-in-function-declaration'))()); + this.registerRule(new (require('../rules/disallow-spaces-in-function-declaration'))()); + + this.registerRule(new (require('../rules/require-spaces-in-call-expression'))()); + this.registerRule(new (require('../rules/disallow-spaces-in-call-expression'))()); + + this.registerRule(new (require('../rules/validate-parameter-separator'))()); + + this.registerRule(new (require('../rules/require-capitalized-constructors'))()); + + this.registerRule(new (require('../rules/safe-context-keyword'))()); + + this.registerRule(new (require('../rules/require-dot-notation'))()); + + this.registerRule(new (require('../rules/require-space-after-line-comment'))()); + this.registerRule(new (require('../rules/disallow-space-after-line-comment'))()); + + this.registerRule(new (require('../rules/require-anonymous-functions'))()); + this.registerRule(new (require('../rules/disallow-anonymous-functions'))()); + + this.registerRule(new (require('../rules/require-function-declarations'))()); + this.registerRule(new (require('../rules/disallow-function-declarations'))()); + + this.registerRule(new (require('../rules/require-capitalized-comments'))()); + this.registerRule(new (require('../rules/disallow-capitalized-comments'))()); +}; + +/** + * Registers built-in Code Style cheking presets. + */ +Configuration.prototype.registerDefaultPresets = function() { + // https://github.com/airbnb/javascript + this.registerPreset('airbnb', require('../../presets/airbnb.json')); + + // http://javascript.crockford.com/code.html + this.registerPreset('crockford', require('../../presets/crockford.json')); + + // https://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml + this.registerPreset('google', require('../../presets/google.json')); + + // https://contribute.jquery.org/style-guide/js/ + this.registerPreset('jquery', require('../../presets/jquery.json')); + + // https://github.com/mrdoob/three.js/wiki/Mr.doob's-Code-Style%E2%84%A2 + this.registerPreset('mdcs', require('../../presets/mdcs.json')); + + // https://www.mediawiki.org/wiki/Manual:Coding_conventions/JavaScript + this.registerPreset('wikimedia', require('../../presets/wikimedia.json')); + + // https://github.com/ymaps/codestyle/blob/master/js.md + this.registerPreset('yandex', require('../../presets/yandex.json')); +}; + +module.exports = Configuration; diff --git a/lib/config/node-configuration.js b/lib/config/node-configuration.js new file mode 100644 index 000000000..6642cadab --- /dev/null +++ b/lib/config/node-configuration.js @@ -0,0 +1,49 @@ +var path = require('path'); +var glob = require('glob'); +var util = require('util'); +var Configuration = require('./configuration'); + +/** + * nodejs-compatible configuration module. + * + * @name NodeConfiguration + * @augments Configuration + * @constructor + */ +function NodeConfiguration() { + Configuration.call(this); +} + +util.inherits(NodeConfiguration, Configuration); + +/** + * Loads plugin data. + * + * @param {String|function(Configuration)} plugin + * @protected + */ +NodeConfiguration.prototype._loadPlugin = function(plugin) { + if (typeof plugin === 'string') { + plugin = require(plugin); + } + Configuration.prototype._loadPlugin.call(this, plugin); +}; + +/** + * Loads additional rule. + * + * @param {string|Rule} additionalRule + * @protected + */ +NodeConfiguration.prototype._loadAdditionalRule = function(additionalRule) { + if (typeof additionalRule === 'string') { + glob.sync(path.resolve(process.cwd(), additionalRule)).forEach(function(path) { + var Rule = require(path); + Configuration.prototype._loadAdditionalRule.call(this, new Rule()); + }, this); + } else { + Configuration.prototype._loadAdditionalRule.call(this, additionalRule); + } +}; + +module.exports = NodeConfiguration; diff --git a/lib/options/additional-rules.js b/lib/options/additional-rules.js deleted file mode 100644 index 15e18473f..000000000 --- a/lib/options/additional-rules.js +++ /dev/null @@ -1,16 +0,0 @@ -var path = require('path'); -var glob = require('glob'); - -module.exports = function(config, instance, cwd) { - (config.additionalRules || []).forEach(function(pattern) { - glob.sync(path.resolve(cwd, pattern)).map(function(path) { - var Rule = require(path); - instance.registerRule(new Rule()); - }); - }); - - Object.defineProperty(config, 'additionalRules', { - value: config.additionalRules, - enumerable: false - }); -}; diff --git a/lib/options/exclude-files.js b/lib/options/exclude-files.js deleted file mode 100644 index ccc44530e..000000000 --- a/lib/options/exclude-files.js +++ /dev/null @@ -1,15 +0,0 @@ -var path = require('path'); -var minimatch = require('minimatch'); - -module.exports = function(config, instance, cwd) { - instance._excludes = (config.excludeFiles || []).map(function(pattern) { - return new minimatch.Minimatch(path.resolve(cwd, pattern), { - dot: true - }); - }); - - Object.defineProperty(config, 'excludeFiles', { - value: config.excludeFiles, - enumerable: false - }); -}; diff --git a/lib/options/file-extensions.js b/lib/options/file-extensions.js deleted file mode 100644 index 461777509..000000000 --- a/lib/options/file-extensions.js +++ /dev/null @@ -1,20 +0,0 @@ -var DEFAULT_FILE_EXTENSIONS = ['.js']; - -module.exports = function(config, instance) { - if (typeof config.fileExtensions === 'string') { - instance._fileExtensions = [config.fileExtensions.toLowerCase()]; - } else if (Array.isArray(config.fileExtensions)) { - instance._fileExtensions = config.fileExtensions.map( - function(s) { - return s.toLowerCase(); - } - ); - } else { - instance._fileExtensions = DEFAULT_FILE_EXTENSIONS; - } - - Object.defineProperty(config, 'fileExtensions', { - value: config.fileExtensions, - enumerable: false - }); -}; diff --git a/lib/options/max-errors.js b/lib/options/max-errors.js deleted file mode 100644 index 2b4936254..000000000 --- a/lib/options/max-errors.js +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Limits the total number of errors reported - * - * @param {Object} config - * @param {StringСhecker} instance - */ -module.exports = function(config, instance) { - instance._maxErrors = config.maxErrors; - - Object.defineProperty(config, 'maxErrors', { - value: Number(config.maxErrors), - enumerable: false - }); -}; diff --git a/lib/string-checker.js b/lib/string-checker.js index c48116c03..41fb7f894 100644 --- a/lib/string-checker.js +++ b/lib/string-checker.js @@ -2,7 +2,7 @@ var esprima = require('esprima'); var Errors = require('./errors'); var JsFile = require('./js-file'); var preset = require('./options/preset'); -var maxErrors = require('./options/max-errors'); +var Configuration = require('./config/configuration'); /** * Starts Code Style checking process. @@ -12,11 +12,14 @@ var maxErrors = require('./options/max-errors'); */ var StringChecker = function(verbose) { this._rules = []; - this._activeRules = []; + this._configuredRules = []; this._config = {}; this._verbose = verbose || false; this._errorsFound = 0; this._maxErrorsExceeded = false; + + this._configuration = this._createConfiguration(); + this._configuration.registerDefaultPresets(); }; StringChecker.prototype = { @@ -26,125 +29,14 @@ StringChecker.prototype = { * @param {Rule} rule */ registerRule: function(rule) { - this._rules.push(rule); + this._configuration.registerRule(rule); }, /** * Registers built-in Code Style cheking rules. */ registerDefaultRules: function() { - this.registerRule(new (require('./rules/require-curly-braces'))()); - this.registerRule(new (require('./rules/require-multiple-var-decl'))()); - this.registerRule(new (require('./rules/disallow-multiple-var-decl'))()); - this.registerRule(new (require('./rules/disallow-empty-blocks'))()); - this.registerRule(new (require('./rules/require-space-after-keywords'))()); - this.registerRule(new (require('./rules/disallow-space-after-keywords'))()); - this.registerRule(new (require('./rules/require-parentheses-around-iife'))()); - - /* deprecated rules */ - this.registerRule(new (require('./rules/require-left-sticked-operators'))()); - this.registerRule(new (require('./rules/disallow-left-sticked-operators'))()); - this.registerRule(new (require('./rules/require-right-sticked-operators'))()); - this.registerRule(new (require('./rules/disallow-right-sticked-operators'))()); - this.registerRule(new (require('./rules/validate-jsdoc'))()); - /* deprecated rules (end) */ - - this.registerRule(new (require('./rules/require-operator-before-line-break'))()); - this.registerRule(new (require('./rules/disallow-implicit-type-conversion'))()); - this.registerRule(new (require('./rules/require-camelcase-or-uppercase-identifiers'))()); - this.registerRule(new (require('./rules/disallow-keywords'))()); - this.registerRule(new (require('./rules/disallow-multiple-line-breaks'))()); - this.registerRule(new (require('./rules/disallow-multiple-line-strings'))()); - this.registerRule(new (require('./rules/validate-line-breaks'))()); - this.registerRule(new (require('./rules/validate-quote-marks'))()); - this.registerRule(new (require('./rules/validate-indentation'))()); - this.registerRule(new (require('./rules/disallow-trailing-whitespace'))()); - this.registerRule(new (require('./rules/disallow-mixed-spaces-and-tabs'))()); - this.registerRule(new (require('./rules/require-keywords-on-new-line'))()); - this.registerRule(new (require('./rules/disallow-keywords-on-new-line'))()); - this.registerRule(new (require('./rules/require-line-feed-at-file-end'))()); - this.registerRule(new (require('./rules/maximum-line-length'))()); - this.registerRule(new (require('./rules/require-yoda-conditions'))()); - this.registerRule(new (require('./rules/disallow-yoda-conditions'))()); - this.registerRule(new (require('./rules/require-spaces-inside-object-brackets'))()); - this.registerRule(new (require('./rules/require-spaces-inside-array-brackets'))()); - this.registerRule(new (require('./rules/require-spaces-inside-parentheses'))()); - this.registerRule(new (require('./rules/disallow-spaces-inside-object-brackets'))()); - this.registerRule(new (require('./rules/disallow-spaces-inside-array-brackets'))()); - this.registerRule(new (require('./rules/disallow-spaces-inside-parentheses'))()); - this.registerRule(new (require('./rules/require-blocks-on-newline'))()); - this.registerRule(new (require('./rules/require-space-after-object-keys'))()); - this.registerRule(new (require('./rules/require-space-before-object-values'))()); - this.registerRule(new (require('./rules/disallow-space-after-object-keys'))()); - this.registerRule(new (require('./rules/disallow-space-before-object-values'))()); - this.registerRule(new (require('./rules/disallow-quoted-keys-in-objects'))()); - this.registerRule(new (require('./rules/disallow-dangling-underscores'))()); - this.registerRule(new (require('./rules/require-aligned-object-values'))()); - - this.registerRule(new (require('./rules/disallow-padding-newlines-in-blocks'))()); - this.registerRule(new (require('./rules/require-padding-newlines-in-blocks'))()); - this.registerRule(new (require('./rules/require-padding-newlines-in-objects'))()); - this.registerRule(new (require('./rules/disallow-padding-newlines-in-objects'))()); - this.registerRule(new (require('./rules/require-newline-before-block-statements'))()); - this.registerRule(new (require('./rules/disallow-newline-before-block-statements'))()); - - this.registerRule(new (require('./rules/disallow-trailing-comma'))()); - this.registerRule(new (require('./rules/require-trailing-comma'))()); - - this.registerRule(new (require('./rules/disallow-comma-before-line-break'))()); - this.registerRule(new (require('./rules/require-comma-before-line-break'))()); - - this.registerRule(new (require('./rules/disallow-space-before-block-statements.js'))()); - this.registerRule(new (require('./rules/require-space-before-block-statements.js'))()); - - this.registerRule(new (require('./rules/disallow-space-before-postfix-unary-operators.js'))()); - this.registerRule(new (require('./rules/require-space-before-postfix-unary-operators.js'))()); - - this.registerRule(new (require('./rules/disallow-space-after-prefix-unary-operators.js'))()); - this.registerRule(new (require('./rules/require-space-after-prefix-unary-operators.js'))()); - - this.registerRule(new (require('./rules/disallow-space-before-binary-operators'))()); - this.registerRule(new (require('./rules/require-space-before-binary-operators'))()); - - this.registerRule(new (require('./rules/disallow-space-after-binary-operators'))()); - this.registerRule(new (require('./rules/require-space-after-binary-operators'))()); - - this.registerRule(new (require('./rules/require-spaces-in-conditional-expression'))()); - this.registerRule(new (require('./rules/disallow-spaces-in-conditional-expression'))()); - - this.registerRule(new (require('./rules/require-spaces-in-function'))()); - this.registerRule(new (require('./rules/disallow-spaces-in-function'))()); - this.registerRule(new (require('./rules/require-spaces-in-function-expression'))()); - this.registerRule(new (require('./rules/disallow-spaces-in-function-expression'))()); - this.registerRule(new (require('./rules/require-spaces-in-anonymous-function-expression'))()); - this.registerRule(new (require('./rules/disallow-spaces-in-anonymous-function-expression'))()); - this.registerRule(new (require('./rules/require-spaces-in-named-function-expression'))()); - this.registerRule(new (require('./rules/disallow-spaces-in-named-function-expression'))()); - this.registerRule(new (require('./rules/require-spaces-in-function-declaration'))()); - this.registerRule(new (require('./rules/disallow-spaces-in-function-declaration'))()); - - this.registerRule(new (require('./rules/require-spaces-in-call-expression'))()); - this.registerRule(new (require('./rules/disallow-spaces-in-call-expression'))()); - - this.registerRule(new (require('./rules/validate-parameter-separator'))()); - - this.registerRule(new (require('./rules/require-capitalized-constructors'))()); - - this.registerRule(new (require('./rules/safe-context-keyword'))()); - - this.registerRule(new (require('./rules/require-dot-notation'))()); - - this.registerRule(new (require('./rules/require-space-after-line-comment'))()); - this.registerRule(new (require('./rules/disallow-space-after-line-comment'))()); - - this.registerRule(new (require('./rules/require-anonymous-functions'))()); - this.registerRule(new (require('./rules/disallow-anonymous-functions'))()); - - this.registerRule(new (require('./rules/require-function-declarations'))()); - this.registerRule(new (require('./rules/disallow-function-declarations'))()); - - this.registerRule(new (require('./rules/require-capitalized-comments'))()); - this.registerRule(new (require('./rules/disallow-capitalized-comments'))()); + this._configuration.registerDefaultRules(); }, /** @@ -152,7 +44,7 @@ StringChecker.prototype = { * @return {Object} */ getProcessedConfig: function() { - return this._config; + return this._configuration.getProcessedConfig(); }, /** @@ -161,34 +53,10 @@ StringChecker.prototype = { * @param {Object} config */ configure: function(config) { - maxErrors(config, this); - - if (config.preset && !preset.exists(config.preset)) { - throw new Error(preset.getDoesNotExistError(config.preset)); - } - - preset.extend(config); - - var configRules = Object.keys(config); - var activeRules = this._activeRules; + this._configuration.load(config); - this._config = config; - this._rules.forEach(function(rule) { - var ruleOptionName = rule.getOptionName(); - - if (config.hasOwnProperty(ruleOptionName)) { - - // Do not configure the rule if it's equals to null (#203) - if (config[ruleOptionName] !== null) { - rule.configure(config[ruleOptionName]); - } - activeRules.push(rule); - configRules.splice(configRules.indexOf(ruleOptionName), 1); - } - }); - if (configRules.length > 0) { - throw new Error('Unsupported rules: ' + configRules.join(', ')); - } + this._configuredRules = this._configuration.getConfiguredRules(); + this._maxErrors = this._configuration.getMaxErrors(); }, /** @@ -220,7 +88,7 @@ StringChecker.prototype = { return errors; } - this._activeRules.forEach(function(rule) { + this._configuredRules.forEach(function(rule) { // Do not process the rule if it's equals to null (#203) if (this._config[rule.getOptionName()] !== null) { errors.setCurrentRule(rule.getOptionName()); @@ -233,7 +101,7 @@ StringChecker.prototype = { return (a.line - b.line) || (a.column - b.column); }); - if (this._maxErrors !== undefined) { + if (this._maxErrors !== null) { if (!this._maxErrorsExceeded) { this._maxErrorsExceeded = this._errorsFound + errors.getErrorCount() > this._maxErrors; } @@ -253,6 +121,25 @@ StringChecker.prototype = { */ maxErrorsExceeded: function() { return this._maxErrorsExceeded; + }, + + /** + * Returns new configuration instance. + * + * @protected + * @returns {Configuration} + */ + _createConfiguration: function() { + return new Configuration(); + }, + + /** + * Returns current configuration instance. + * + * @returns {Configuration} + */ + getConfiguration: function() { + return this._configuration; } }; diff --git a/test/mocha.opts b/test/mocha.opts index 4ebbf346b..5a7f33dfc 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/*.js test/rules/*.js test/reporters/*.js -R dot -u bdd diff --git a/test/options/additional-rules.js b/test/options/additional-rules.js deleted file mode 100644 index 79fc7c1a0..000000000 --- a/test/options/additional-rules.js +++ /dev/null @@ -1,39 +0,0 @@ -var Checker = require('../../lib/checker'); -var configFile = require('../../lib/cli-config'); -var assert = require('assert'); - -describe('options/additional-rules', function() { - var checker; - - beforeEach(function() { - checker = new Checker(); - }); - - it('should add additional rules', function() { - checker.configure({ - additionalRules: ['test/data/rules/*.js'], - testAdditionalRules: true - }); - - assert(checker.checkString('').getErrorCount() === 1); - }); - - it('should resolve rules path relative to config location', function() { - var checker = new Checker(); - checker.configure(configFile.load('./test/data/configs/additionalRules/.jscs.json')); - - assert(checker.checkString('').getErrorCount() === 1); - }); - - it('should be present in config after initialization', function() { - checker.configure({ - additionalRules: ['test/data/rules/*.js'], - testAdditionalRules: true - }); - - var config = checker.getProcessedConfig(); - - assert(config.additionalRules !== undefined); - assert(Object.getOwnPropertyDescriptor(config, 'additionalRules').enumerable === false); - }); -}); diff --git a/test/options/exclude-files.js b/test/options/exclude-files.js deleted file mode 100644 index 8652c63de..000000000 --- a/test/options/exclude-files.js +++ /dev/null @@ -1,174 +0,0 @@ -var Checker = require('../../lib/checker'); -var configFile = require('../../lib/cli-config'); -var assert = require('assert'); -var Vow = require('vow'); - -describe('options/exclude-files', function() { - var checker; - - beforeEach(function() { - checker = new Checker(); - checker.registerDefaultRules(); - }); - - describe('use config in script', function() { - it('should not report any errors', function() { - checker.configure({ - excludeFiles: ['test/data/configs/excludeFiles/exclude-files.js'], - disallowKeywords: ['with'] - }); - - return checker.checkFile('./test/data/configs/excludeFiles/exclude-files.js').then(function(errors) { - assert(errors === null); - }); - }); - - it('should allow patterns to match filenames starting with a period', function() { - checker.configure({ - excludeFiles: ['test/data/configs/excludeFiles/**'], - disallowKeywords: ['with'] - }); - - return checker.checkFile('./test/data/configs/excludeFiles/.withdot/error.js').then(function(errors) { - assert(errors === null); - }); - }); - - it('should resolve pattern to process.cwd', function() { - var results = []; - checker.configure({ - excludeFiles: ['test/data/exclude-files.js'], - disallowKeywords: ['with'] - }); - - // errors - results.push(checker.checkFile('./test/data/configs/excludeFiles/script.js').then(function(errors) { - assert(errors.getErrorList().length === 0); - })); - results.push(checker.checkFile('./test/data/configs/excludeFiles/nested/script.js').then(function(errors) { - assert(errors.getErrorList().length === 0); - })); - - return Vow.allResolved(results); - }); - - it('should resolve pattern to process.cwd', function() { - var results = []; - checker.configure({ - excludeFiles: ['test/data/exclude-files.js'], - disallowKeywords: ['with'] - }); - - // errors - results.push(checker.checkFile('./test/data/configs/excludeFiles/script.js').then(function(errors) { - assert(errors.getErrorList().length === 0); - })); - results.push(checker.checkFile('./test/data/configs/excludeFiles/nested/script.js').then(function(errors) { - assert(errors.getErrorList().length === 0); - })); - - return Vow.allResolved(results); - }); - }); - - describe('should resolve pattern relative to config file', function() { - it('(pattern: *.js)', function() { - var results = []; - checker.configure(configFile.load('./test/data/configs/excludeFiles/test1.jscs.json')); - - // ok - results.push(checker.checkFile('./test/data/configs/excludeFiles/exclude-files.js').then(function(errors) { - assert(errors === null); - })); - - // errors - results.push(checker.checkFile('./test/data/exclude-files.js').then(function(errors) { - assert(errors.getErrorList().length === 0); - })); - results.push(checker.checkFile('./test/data/configs/excludeFiles/nested/exclude-files.js') - .then(function(errors) { - assert(errors === null); - }) - ); - - return Vow.allResolved(results); - }); - - it('(pattern: exclude-files.js)', function() { - var results = []; - checker.configure(configFile.load('./test/data/configs/excludeFiles/test2.jscs.json')); - - // ok - results.push(checker.checkFile('./test/data/configs/excludeFiles/exclude-files.js').then(function(errors) { - assert(errors === null); - })); - - // errors - results.push(checker.checkFile('./test/data/exclude-files.js').then(function(errors) { - assert(errors.getErrorList().length === 0); - })); - results.push(checker.checkFile('./test/data/configs/excludeFiles/nested/exclude-files.js') - .then(function(errors) { - assert(errors === null); - }) - ); - - return Vow.allResolved(results); - }); - - it('(pattern: */exclude-files.js)', function() { - var results = []; - checker.configure(configFile.load('./test/data/configs/excludeFiles/test3.jscs.json')); - - // ok - results.push(checker.checkFile('./test/data/configs/excludeFiles/nested/exclude-files.js') - .then(function(errors) { - assert(errors === null); - }) - ); - - // errors - results.push(checker.checkFile('./test/data/exclude-files.js').then(function(errors) { - assert(errors.getErrorList().length === 0); - })); - results.push(checker.checkFile('./test/data/configs/excludeFiles/exclude-files.js').then(function(errors) { - assert(errors.getErrorList().length === 0); - })); - - return Vow.allResolved(results); - }); - - it('(pattern: ../**/exclude-files.js)', function() { - var results = []; - checker.configure(configFile.load('./test/data/configs/excludeFiles/test4.jscs.json')); - - // ok - results.push(checker.checkFile('./test/data/configs/excludeFiles/nested/exclude-files.js') - .then(function(errors) { - assert(errors === null); - }) - ); - results.push(checker.checkFile('./test/data/configs/excludeFiles/exclude-files.js').then(function(errors) { - assert(errors === null); - })); - - // errors - results.push(checker.checkFile('./test/data/exclude-files.js').then(function(errors) { - assert(errors.getErrorList().length === 0); - })); - - return Vow.allResolved(results); - }); - }); - - it('should be present in config after initialization', function() { - checker.configure({ - excludeFiles: [] - }); - - var config = checker.getProcessedConfig(); - - assert(config.excludeFiles !== undefined); - assert(Object.getOwnPropertyDescriptor(config, 'excludeFiles').enumerable === false); - }); -}); diff --git a/test/options/file-extensions.js b/test/options/file-extensions.js deleted file mode 100644 index d054e9ad9..000000000 --- a/test/options/file-extensions.js +++ /dev/null @@ -1,124 +0,0 @@ -var path = require('path'); -var fs = require('fs'); - -var Checker = require('../../lib/checker'); -var assert = require('assert'); - -describe('options/file-extensions', function() { - var checker; - - beforeEach(function() { - checker = new Checker(); - checker.registerDefaultRules(); - }); - - describe('default config', function() { - beforeEach(function() { - checker.configure({ - disallowKeywords: ['with'] - }); - }); - - it('should report errors for matching extensions (case insensitive) in directory with default config', - function() { - return checker.checkDirectory('./test/data/options/file-extensions').then(function(errors) { - assert(errors.length === 2); - }); - } - ); - }); - - describe('custom config', function() { - it('should report errors for matching extensions with custom config', function() { - checker.configure({ - fileExtensions: ['.jsx'], - disallowKeywords: ['with'] - }); - - return checker.checkDirectory('./test/data/options/file-extensions').then(function(errors) { - assert(errors.length === 1); - }); - }); - it('should report errors for matching extensions (case insensitive) with custom config', function() { - checker.configure({ - fileExtensions: ['.JS'], - disallowKeywords: ['with'] - }); - - return checker.checkDirectory('./test/data/options/file-extensions').then(function(errors) { - assert(errors.length === 2); - }); - }); - it('should report errors for matching extensions (case insensitive) with string value', function() { - checker.configure({ - fileExtensions: '.JS', - disallowKeywords: ['with'] - }); - - assert(checker.checkFile('./test/data/options/file-extensions/file-extensions-2.jS') !== null); - - return checker.checkDirectory('./test/data/options/file-extensions').then(function(errors) { - assert(errors.length === 2); - }); - }); - it('should report errors for matching extensions with custom config with multiple extensions', function() { - checker.configure({ - fileExtensions: ['.js', '.jsx'], - disallowKeywords: ['with'] - }); - - assert(checker.checkFile('./test/data/options/file-extensions/file-extensions.js') !== null); - assert(checker.checkFile('./test/data/options/file-extensions/file-extensions.jsx') !== null); - - return checker.checkDirectory('./test/data/options/file-extensions').then(function(errors) { - assert(errors.length === 3); - }); - }); - it('should report errors for matching extensions with Array *', function() { - var testPath = './test/data/options/file-extensions'; - - checker.configure({ - fileExtensions: ['*'], - disallowKeywords: ['with'] - }); - - return checker.checkDirectory(testPath).then(function(errors) { - assert(errors.length === fs.readdirSync(testPath).length); - }); - }); - it('should report errors for matching extensions with string *', function() { - var testPath = './test/data/options/file-extensions'; - - checker.configure({ - fileExtensions: '*', - disallowKeywords: ['with'] - }); - - return checker.checkDirectory(testPath).then(function(errors) { - assert(errors.length === fs.readdirSync(testPath).length); - }); - }); - - it('should report errors for file whose fullname is the same as matching extension', function() { - checker.configure({ - fileExtensions: 'file-extensions', - disallowKeywords: ['with'] - }); - - return checker.checkDirectory('./test/data/options/file-extensions').then(function(errors) { - assert(errors.length === 1); - }); - }); - }); - - it('should be present in config after initialization', function() { - checker.configure({ - fileExtensions: 'test' - }); - - var config = checker.getProcessedConfig(); - - assert(config.fileExtensions !== undefined); - assert(Object.getOwnPropertyDescriptor(config, 'fileExtensions').enumerable === false); - }); -}); diff --git a/test/options/preset.js b/test/options/preset.js deleted file mode 100644 index 4d03cd3a4..000000000 --- a/test/options/preset.js +++ /dev/null @@ -1,86 +0,0 @@ -var Checker = require('../../lib/checker'); -var preset = require('../../lib/options/preset'); -var assert = require('assert'); - -describe('options/preset', function() { - testPreset('airbnb'); - testPreset('crockford'); - testPreset('google'); - testPreset('jquery'); - testPreset('mdcs'); - testPreset('wikimedia'); - testPreset('yandex'); - - /** - * Helper to test a given preset's configuration against its test file - * - * Expects the given preset to have a configuration in /presets - * and real code taken from that project in /test/data/options/preset - * - * @example testPreset('google') - * @param {String} presetName - */ - function testPreset(presetName) { - describe(presetName + ' preset', function() { - var checker = new Checker(); - var preset = require('../../presets/' + presetName); - - checker.registerDefaultRules(); - checker.configure({ - preset: presetName - }); - - var config = checker.getProcessedConfig(); - - it('should set the correct rules', function() { - assert(config.requireCurlyBraces === preset.requireCurlyBraces); - assert(config.config !== preset); - }); - - it('should not report any errors from the sample file', function() { - return checker.checkFile('./test/data/options/preset/' + presetName + '.js').then(function(errors) { - assert(errors.isEmpty()); - }); - }); - }); - } - - describe('getDoesNotExistError', function() { - it('returns the correct error message', function() { - assert(preset.getDoesNotExistError('foo') === 'Preset "foo" does not exist'); - }); - }); - - describe('exists', function() { - it('returns true for existing presets', function() { - assert(preset.exists('jquery')); - }); - - it('returns false for non-existant presets', function() { - assert(!preset.exists('aPresetThatWillNeverExist')); - }); - }); - - describe('extend', function() { - it('returns true if preset not present in config', function() { - assert(preset.extend({ not: 'real' })); - }); - - it('returns false if provided preset is not a real preset', function() { - assert(!preset.extend({ preset: 'aPresetThatWillNeverExist' })); - }); - - it('removes the preset key from the config, and add its rules to the config', function() { - var config = { - preset: 'jquery', - fakeRule: true - }; - - preset.extend(config); - - assert(!config.preset); - assert(config.requireOperatorBeforeLineBreak); - assert(config.fakeRule); - }); - }); -}); diff --git a/test/string-checker.js b/test/string-checker.js index 62e0cbc37..b362503c4 100644 --- a/test/string-checker.js +++ b/test/string-checker.js @@ -66,7 +66,7 @@ describe('modules/string-checker', function() { assert(false); } catch (e) { - assert(e.toString() === 'Error: Preset "not-exist" does not exist'); + assert.equal(e.toString(), 'AssertionError: Preset "not-exist" does not exist'); } });