Repository: tclindner/npm-package-json-lint
Pull request #22 (2017-02-18 23:23:15)

2.0.0


Patch:
diff --git a/.travis.yml b/.travis.yml
index 51b31ac..f012d74 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -13,6 +13,7 @@ node_js:
   - "4"
   - "5"
   - "6"
+  - "7"
 
 cache:
   directories:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d82c241..041d291 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,14 @@ This project adheres to [Semantic Versioning](http://semver.org/).
 
 ### Removed
 
+## [2.0.0] - 2017-02-18
+### Added
+- Support configuration extension!! Now you can add "extends" to your rc file to extend a based configuration. This is great for sharing a standard ruleset between many projects.  Please see the [wiki](https://github.com/tclindner/npm-package-json-lint/wiki) for more information.
+- An optional cli flag for controlling rule severity. Please see the [README.md](README.md) for examples.
+
+### Removed
+- Default configuration is no longer provided. Please see the new default config module, [npm-package-json-lint-config-default](https://github.com/tclindner/npm-package-json-lint-config-default)
+
 ## [1.4.0] - 2016-10-15
 ### Added
 - New rule: [valid-values-license](https://github.com/tclindner/npm-package-json-lint/wiki/valid-values-license)
diff --git a/README.md b/README.md
index aa954bc..a61ecb0 100755
--- a/README.md
+++ b/README.md
@@ -53,10 +53,13 @@ First thing first, let's make sure you have the necessary pre-requisites.
 ### Examples
 
 Run a specific rule, require-author, on a file relative to the current working directory.
-`pjl-cli -f "../relative-path/package.json" -r "require-author: 'error'"`
+`pjl-cli -f "../relative-path/package.json" -r "require-author"`
 
 Run a specific rule, require-author, ignoring warnings on a file relative to the current working directory.
-`pjl-cli -f "../relative-path/package.json" -r "require-author: 'error'" --ignore-warnings`
+`pjl-cli -f "../relative-path/package.json" -r "require-author" --ignore-warnings`
+
+Run a specific rule, require-author, set severity to warning on a file relative to the current working directory.
+`pjl-cli -f "../relative-path/package.json" -r "require-author" -s "warning"`
 
 Run using the config in `.npmpackagejsonlintrc` on a file relative to the current working directory.
 `pjl-cli -f "../relative-path/package.json" -c "./.npmpackagejsonlintrc"`
@@ -66,7 +69,7 @@ Run using the default config on a file relative to the current working directory
 
 ## Lint Rules
 
-npm-package-json-lint has a configurable set of rules. Please see the [wiki](https://github.com/tclindner/npm-package-json-lint/wiki) for a full list of available rules. By [default](src/defaultConfig.js) only type checks and name/version rules are enforced. This is the bare minimum configuration.
+npm-package-json-lint has a configurable set of rules. Please see the [wiki](https://github.com/tclindner/npm-package-json-lint/wiki) for a full list of available rules. By default no rules are enabled. If you would like to use npm-package-json-lint's default ruleset, please see [npm-package-json-lint-config-default](https://github.com/tclindner/npm-package-json-lint-config-default).
 
 Each rule contains the following properties:
 
@@ -75,17 +78,22 @@ Each rule contains the following properties:
   3. Message - example: author is required
   4. Rule Type - example: required
 
-As mentioned in the "Commands and configuration" section there are two ways to specify rule sets. The first is using `--rule` to specify a given rule. This will run npm-package-json-lint with just this rule. The second is using `--rules-file` to specify a JSON file, named [`.npmpackagejsonlintrc`](https://github.com/tclindner/npm-package-json-lint/wiki/npm-package-json-lint-rc), to run a set of rules. If neither of the options above are specified then npm-package-json-lint looks for a global [`.npmpackagejsonlintrc`](https://github.com/tclindner/npm-package-json-lint/wiki/npm-package-json-lint-rc) file in the root of your user directory. Finally, if a global [`.npmpackagejsonlintrc`](https://github.com/tclindner/npm-package-json-lint/wiki/npm-package-json-lint-rc) file doesn't exist then the [defaults](src/defaultConfig.js) are used.
+As mentioned in the "Commands and configuration" section there are two ways to specify rule sets. The first is using `--rule` to specify a given rule. This will run npm-package-json-lint with just this rule. The second is using `--rules-file` to specify a JSON file, named [`.npmpackagejsonlintrc`](https://github.com/tclindner/npm-package-json-lint/wiki/npm-package-json-lint-rc), to run a set of rules. If neither of the options above are specified then npm-package-json-lint looks for a global [`.npmpackagejsonlintrc`](https://github.com/tclindner/npm-package-json-lint/wiki/npm-package-json-lint-rc) file in the root of your user directory.
 
 ### Configuring rules
 
-npm-package-json-lint rules can either be run as an `error` or a `warning`.
+npm-package-json-lint rules can either be run as an `error`, `warning`, or `off`.
 
 * "warning" - run the rule as a warning
 * "error" - run the rule as an error
+* "off" - disables the rule
 
 Ex: `"require-author": "error"`
 
+## Migrating from v10.x.x to 2.x.x
+
+Please see the [migration guide](https://github.com/tclindner/npm-package-json-lint/wiki/migrating-from-v1-to-v2).
+
 ## Migrating from v0.x.x to 1.x.x
 
 Please see the [migration guide](https://github.com/tclindner/npm-package-json-lint/wiki/migrating-from-v0-to-v1).
@@ -101,6 +109,7 @@ Please see [CHANGELOG.md](CHANGELOG.md).
 ## Related
 
 * [grunt-npm-package-json-lint](https://github.com/tclindner/grunt-npm-package-json-lint): Grunt Wrapper for npm-package-json-lint
+* [npm-package-json-lint-config-default](https://github.com/tclindner/npm-package-json-lint-config-default): Shared default configuration module for npm-package-json-lint
 
 ## License
 
diff --git a/grunt/coverage-check.js b/grunt/coverage-check.js
index e74b6d6..4738510 100644
--- a/grunt/coverage-check.js
+++ b/grunt/coverage-check.js
@@ -6,9 +6,9 @@ module.exports = function(grunt) {
       options: {
         thresholds: {
           statements: 100,
-          branches: 98,
+          branches: 99,
           lines: 100,
-          functions: 100
+          functions: 99
         },
         dir: 'coverage',
         root: 'tests'
diff --git a/grunt/watch.js b/grunt/watch.js
deleted file mode 100755
index 4d966de..0000000
--- a/grunt/watch.js
+++ /dev/null
@@ -1,25 +0,0 @@
-// Monitor files for changes and run tasks
-module.exports = function(grunt) {
-  grunt.config.set('watch', {
-    json: {
-      files: [
-        '**/*.json'
-      ],
-      tasks: [
-        'jsonlint'
-      ]
-    },
-    javascript: {
-      files: [
-        'src/**/*.js',
-        'tests/**/*.js'
-      ],
-      tasks: [
-        'lint',
-        'mochaTest:local'
-      ]
-    }
-  });
-
-  grunt.loadNpmTasks('grunt-contrib-watch');
-};
diff --git a/package.json b/package.json
index 17ac101..40be42b 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "npm-package-json-lint",
-  "version": "1.4.0",
+  "version": "2.0.0",
   "description": "CLI app for linting package.json files.",
   "keywords": [
     "lint",
@@ -24,33 +24,32 @@
   },
   "main": "src/NpmPackageJsonLint.js",
   "dependencies": {
-    "chalk": "^1.1.1",
+    "chalk": "^1.1.3",
     "commander": "^2.9.0",
     "in-array": "^0.1.2",
     "is-plain-obj": "^1.1.0",
     "semver": "^5.3.0",
     "user-home": "^2.0.0",
-    "validator": "^6.0.0"
+    "validator": "^6.2.1"
   },
   "devDependencies": {
-    "eslint": "^3.7.1",
-    "eslint-config-tc": "^1.2.0",
+    "eslint": "^3.15.0",
+    "eslint-config-tc": "^1.4.0",
     "eslint-formatter-pretty": "^1.0.0",
     "grunt": "^1.0.1",
     "grunt-bump": "^0.8.0",
     "grunt-contrib-clean": "^1.0.0",
     "grunt-contrib-copy": "^1.0.0",
-    "grunt-contrib-watch": "^1.0.0",
     "grunt-env": "^0.4.4",
     "grunt-eslint": "^19.0.0",
-    "grunt-istanbul": "^0.7.1",
+    "grunt-istanbul": "^0.7.2",
     "grunt-istanbul-coverage": "^0.1.4",
     "grunt-jscs": "^3.0.1",
     "grunt-jsonlint": "^1.1.0",
     "grunt-mocha-test": "^0.13.2",
-    "mocha": "^3.1.0",
-    "should": "^11.1.0",
-    "sinon": "^1.17.5",
+    "mocha": "^3.2.0",
+    "should": "^11.2.0",
+    "sinon": "^1.17.7",
     "time-grunt": "^1.4.0"
   },
   "engines": {
diff --git a/src/Config.js b/src/Config.js
index 8ba00c7..4ee7513 100755
--- a/src/Config.js
+++ b/src/Config.js
@@ -21,14 +21,7 @@ class Config {
       'no-restricted-pre-release-devDependencies'
     ];
 
-    if (this._isConfigPassed(passedConfigParam)) {
-      const passedConfig = this._getPassedConfig(passedConfigParam);
-
-      this.config = Object.assign({}, passedConfig);
-    } else {
-      this.defaultConfig = require('./defaultConfig');
-      this.config = Object.assign({}, this.defaultConfig);
-    }
+    this.passedConfigParam = passedConfigParam;
   }
 
   /**
@@ -36,7 +29,18 @@ class Config {
    * @return {Object} Config object
    */
   get() {
-    return this.config;
+    if (this._isConfigPassed(this.passedConfigParam)) {
+      const passedConfig = this._getPassedConfig(this.passedConfigParam);
+      let extendsConfig = {};
+
+      if (passedConfig.hasOwnProperty('extends')) {
+        extendsConfig = this._getExtendsConfig(passedConfig.extends);
+      }
+
+      return Object.assign({}, extendsConfig, passedConfig.rules);
+    } else {
+      throw new Error('No configuration passed');
+    }
   }
 
   /**
@@ -75,24 +79,62 @@ class Config {
   }
 
   /**
-   * Validates config file
-   * @param  {Object}     rcFileObj   Object version of .npmpackagejsonlintrc file
-   * @return {boolean}                True if validate config is successful
+   * Gets configuration from a extends config module
+   * @param  {String} moduleName  Name of the configuration module
+   * @return {Object}             Configuration object
+   */
+  _getExtendsConfig(moduleName) {
+    const configObj = this._getExtendsConfigModule(moduleName);
+
+    this._validateConfig(configObj);
+
+    return configObj.rules;
+  }
+
+  /**
+   * Loads extends config module
+   * @param  {String} moduleName  Name of the configuration module
+   * @return {Object}             Configuration object
+   */
+  _getExtendsConfigModule(moduleName) {
+    /* istanbul ignore next */
+    return require(moduleName);
+  }
+
+  /**
+   * Validates config object
+   * @param  {Object} rcFileObj   Object version of .npmpackagejsonlintrc file
+   * @return {boolean}            True if validate config is successful
    */
   _validateConfig(rcFileObj) {
-    for (const rule in rcFileObj) {
-      const ruleConfig = rcFileObj[rule];
+    if (rcFileObj.hasOwnProperty('rules')) {
+      this._validateRulesConfig(rcFileObj.rules);
+    } else {
+      throw new Error('`rules` object missing in config');
+    }
+
+    return true;
+  }
+
+  /**
+   * Validates rules object
+   * @param  {Object}     rulesObj   Object version of .npmpackagejsonlintrc file
+   * @return {boolean}               True if validate config is successful
+   */
+  _validateRulesConfig(rulesObj) {
+    for (const rule in rulesObj) {
+      const ruleConfig = rulesObj[rule];
 
       if (Array.isArray(ruleConfig) && inArray(this.arrayRules, rule)) {
         if (typeof ruleConfig[0] !== 'string' || this._isRuleValid(ruleConfig[0])) {
-          throw new Error(`${rule} - first key must be set to "error" or "warning". Currently set to ${ruleConfig[0]}`);
+          throw new Error(`${rule} - first key must be set to "error", "warning", or "off". Currently set to ${ruleConfig[0]}`);
         }
 
         if (!Array.isArray(ruleConfig[1])) {
           throw new Error(`${rule} - second key must be set an array. Currently set to ${ruleConfig[1]}`);
         }
       } else if (typeof ruleConfig !== 'string' || this._isRuleValid(ruleConfig)) {
-        throw new Error(`${rule} - must be set to "error" or "warning". Currently set to ${ruleConfig}`);
+        throw new Error(`${rule} - must be set to "error", "warning", or "off". Currently set to ${ruleConfig}`);
       }
     }
 
@@ -105,7 +147,7 @@ class Config {
    * @return {Boolean}     True if the rule is valid. False if the rule is invalid.
    */
   _isRuleValid(key) {
-    return typeof key === 'string' && key !== 'error' && key !== 'warning';
+    return typeof key === 'string' && key !== 'error' && key !== 'warning' && key !== 'off';
   }
 
 }
diff --git a/src/NpmPackageJsonLint.js b/src/NpmPackageJsonLint.js
index d1d1b29..a2dd458 100755
--- a/src/NpmPackageJsonLint.js
+++ b/src/NpmPackageJsonLint.js
@@ -22,9 +22,8 @@ class NpmPackageJsonLint {
     this.warnings = [];
 
     this.rules = new Rules();
-    this._loadRules();
-
-    this.config = this._getConfig(config);
+    this.rules.load();
+    this.config = config;
   }
 
   /**
@@ -32,20 +31,25 @@ class NpmPackageJsonLint {
    * @return {Object} Results object
    */
   lint() {
-    for (const rule in this.config) {
+    const configObj = this._getConfig(this.config);
+
+    for (const rule in configObj) {
       const ruleModule = this.rules.get(rule);
 
       if (inArray(this.arrayRuleTypes, ruleModule.ruleType)) {
-        const errorWarningSetting = this.config[rule][0];
-        const ruleConfigArray = this.config[rule][1];
-        const lintResult = ruleModule.lint(this.packageJsonData, errorWarningSetting, ruleConfigArray);
+        const errorWarningOffSetting = configObj[rule][0];
+        const ruleConfigArray = configObj[rule][1];
+
+        if (errorWarningOffSetting !== 'off') {
+          this._processResult(ruleModule.lint(this.packageJsonData, errorWarningOffSetting, ruleConfigArray), errorWarningOffSetting);
+        }
 
-        this._processResult(lintResult, errorWarningSetting);
       } else {
-        const errorWarningSetting = this.config[rule];
-        const lintResult = ruleModule.lint(this.packageJsonData, errorWarningSetting);
+        const errorWarningOffSetting = configObj[rule];
 
-        this._processResult(lintResult, errorWarningSetting);
+        if (errorWarningOffSetting !== 'off') {
+          this._processResult(ruleModule.lint(this.packageJsonData, errorWarningOffSetting), errorWarningOffSetting);
+        }
       }
     }
 
@@ -84,14 +88,6 @@ class NpmPackageJsonLint {
     return result;
   }
 
-  /**
-   * Private method for loading rules
-   * @return {undefined}            No return
-   */
-  _loadRules() {
-    this.rules.load();
-  }
-
   /**
    * Private method for loading config
    * @param  {Object|String} config   Object or string with desired configuration
diff --git a/src/cli.js b/src/cli.js
index d6f8fc3..4e81cc0 100755
--- a/src/cli.js
+++ b/src/cli.js
@@ -12,6 +12,7 @@ const Reporter = require('./Reporter');
 const userHome = require('user-home');
 
 const DEFAULT_FILE_NAME = './package.json';
+const DEFAULT_RULE_SEVERITY = 'error';
 const RC_FILE_NAME = '.npmpackagejsonlintrc';
 
 /**
@@ -32,6 +33,7 @@ cliApp.version(pkg.version);
 cliApp.usage(pkg.name);
 cliApp.option('-f, --file <filePath>', `File path including name. Defaults to ${DEFAULT_FILE_NAME}`, DEFAULT_FILE_NAME);
 cliApp.option('-r, --rule <rule name>', 'Valid rule name to check. Defaults to nothing');
+cliApp.option('-s, --rule-severity <rule severity>', `"error" or "warning". Defaults to ${DEFAULT_RULE_SEVERITY}`, DEFAULT_RULE_SEVERITY);
 cliApp.option('-c, --rules-file <filePath>', 'File path of .npmpackagejsonlintrc');
 cliApp.option('-w, --ignore-warnings', 'Ignore warnings');
 cliApp.parse(process.argv);
@@ -52,7 +54,10 @@ let rulesLoaded = false;
 let rulesConfig = {};
 
 if (typeof cliApp.rule !== 'undefined') {
-  rulesConfig[cliApp.rule] = true;
+  const rules = {};
+
+  rules[cliApp.rule] = cliApp.ruleSeverity;
+  rulesConfig.rules = rules;
   rulesLoaded = true;
 }
 
diff --git a/src/defaultConfig.js b/src/defaultConfig.js
deleted file mode 100755
index e8bb130..0000000
--- a/src/defaultConfig.js
+++ /dev/null
@@ -1,33 +0,0 @@
-'use strict';
-
-const defaultConfig = {
-  'require-name': 'error',
-  'require-version': 'error',
-  'name-format': 'error',
-  'version-format': 'error',
-  'bin-type': 'error',
-  'config-type': 'error',
-  'cpu-type': 'error',
-  'dependencies-type': 'error',
-  'description-type': 'error',
-  'devDependencies-type': 'error',
-  'directories-type': 'error',
-  'engines-type': 'error',
-  'files-type': 'error',
-  'homepage-type': 'error',
-  'keywords-type': 'error',
-  'license-type': 'error',
-  'main-type': 'error',
-  'man-type': 'error',
-  'name-type': 'error',
-  'optionalDependencies-type': 'error',
-  'os-type': 'error',
-  'peerDependencies-type': 'error',
-  'preferGlobal-type': 'error',
-  'private-type': 'error',
-  'repository-type': 'error',
-  'scripts-type': 'error',
-  'version-type': 'error'
-};
-
-module.exports = defaultConfig;


Test patch:
diff --git a/tests/unit/ConfigTest.js b/tests/unit/ConfigTest.js
index 6c91460..41d35cf 100755
--- a/tests/unit/ConfigTest.js
+++ b/tests/unit/ConfigTest.js
@@ -6,146 +6,266 @@ const should = require('should');
 const sinon = require('sinon');
 const requireHelper = require('../require_helper');
 const Config = requireHelper('Config');
-const defaultConfig = requireHelper('defaultConfig');
 
 describe('Config Unit Tests', function() {
-  let spy;
+  context('when an empty object is passed', function() {
+    it('the default config object should be returned', function() {
+      const passedConfig = {};
+      const config = new Config(passedConfig);
 
-  beforeEach(function() {
-    const stub = sinon.stub(fs, 'readFileSync');
+      (function() {
+        config.get(passedConfig);
+      }).should.throw('No configuration passed');
+    });
+  });
 
-    stub.returns('{"version-type": "error"}');
+  context('Tests for when a filepath is passed', function() {
+    let spy;
 
-    spy = sinon.spy(path, 'join');
-  });
+    beforeEach(function() {
+      const stub = sinon.stub(fs, 'readFileSync');
 
-  afterEach(function() {
-    fs.readFileSync.restore();
-    path.join.restore();
-  });
+      stub.returns('{"rules": {"version-type": "error"}}');
 
-  context('when a rules object is passed', function() {
-    it('the config object should return that object', function() {
-      const passedConfig = {
-        'version-type': 'error'
-      };
-      const config = new Config(passedConfig);
+      spy = sinon.spy(path, 'join');
+    });
 
-      config.get().should.eql(passedConfig);
+    afterEach(function() {
+      fs.readFileSync.restore();
+      path.join.restore();
     });
-  });
 
-  context('when a relative path string to a .npmpackagejsonlintrc file is passed', function() {
-    it('the config object should return the parsed JSON as an object', function() {
-      const passedConfig = './.npmpackagejsonlintrc';
-      const config = new Config(passedConfig);
-      const obj = {
-        'version-type': 'error'
-      };
+    context('when a relative path string to a .npmpackagejsonlintrc file is passed', function() {
+      it('the config object should return the parsed JSON as an object', function() {
+        const passedConfig = './.npmpackagejsonlintrc';
+        const config = new Config(passedConfig);
+        const expectedConfigObj = {
+          'version-type': 'error'
+        };
+        const result = config.get();
 
-      spy.calledOnce.should.be.true();
-      spy.firstCall.calledWithExactly(__dirname, passedConfig);
-      config.get().should.eql(obj);
+        spy.calledOnce.should.be.true();
+        spy.firstCall.calledWithExactly(__dirname, passedConfig);
+        result.should.eql(expectedConfigObj);
+      });
     });
-  });
 
-  context('when an absolute path string to a .npmpackagejsonlintrc file is passed', function() {
-    it('the config object should return the parsed JSON as an object', function() {
-      const passedConfig = '/Users/awesomeUser/.npmpackagejsonlintrc';
-      const config = new Config(passedConfig);
-      const obj = {
-        'version-type': 'error'
-      };
+    context('when an absolute path string to a .npmpackagejsonlintrc file is passed', function() {
+      it('the config object should return the parsed JSON as an object', function() {
+        const passedConfig = '/Users/awesomeUser/.npmpackagejsonlintrc';
+        const config = new Config(passedConfig);
+        const expectedConfigObj = {
+          'version-type': 'error'
+        };
+        const result = config.get();
 
-      spy.called.should.be.false();
-      config.get().should.eql(obj);
+        spy.called.should.be.false();
+        result.should.eql(expectedConfigObj);
+      });
     });
   });
 
-  context('when an empty object is passed', function() {
-    it('the default config object should be returned', function() {
-      const passedConfig = {};
-      const config = new Config(passedConfig);
+  context('Tests for when a extends is passed', function() {
+    let spy;
 
-      config.get().should.eql(defaultConfig);
+    beforeEach(function() {
+      const stub = sinon.stub(fs, 'readFileSync');
+
+      stub.returns('{"extends": "npm-package-json-lint-config-base", "rules": {"version-type": "off"}}');
+
+      spy = sinon.spy(path, 'join');
     });
-  });
 
-  context('when a valid npmpackagejsonlintrc file object is passed', function() {
-    it('true should be returned', function() {
-      const rcFileObj = {
-        'require-author': 'error',
-        'require-version': 'warning',
-        'valid-values-author': ['error', ['Thomas', 'Lindner', 'Thomas Lindner']],
-        'valid-values-private': ['warning', [true, false]]
-      };
-      const config = new Config(rcFileObj);
+    afterEach(function() {
+      fs.readFileSync.restore();
+      path.join.restore();
+    });
+
+    context('when a config object is passed with an extends node', function() {
+      it('the extends config and rules node should be merged', function() {
+        const passedConfig = './.npmpackagejsonlintrc';
+        const config = new Config(passedConfig);
+        const stubbedGetExtendCfgModule = sinon.stub(config, '_getExtendsConfigModule');
+        const extendsObj = {
+          rules: {
+            'name-type': 'error'
+          }
+        };
+
+        stubbedGetExtendCfgModule.returns(extendsObj);
 
-      config._validateConfig(rcFileObj).should.eql(true);
+        const expectedConfigObj = {
+          'name-type': 'error',
+          'version-type': 'off'
+        };
+        const result = config.get();
+
+        spy.calledOnce.should.be.true();
+        spy.firstCall.calledWithExactly(__dirname, passedConfig);
+        result.should.eql(expectedConfigObj);
+
+        config._getExtendsConfigModule.restore();
+      });
+
+      it('and they both contain the same rule the main rules node should take precedence', function() {
+        const passedConfig = './.npmpackagejsonlintrc';
+        const config = new Config(passedConfig);
+        const stubbedGetExtendCfgModule = sinon.stub(config, '_getExtendsConfigModule');
+        const extendsObj = {
+          rules: {
+            'version-type': 'warning'
+          }
+        };
+
+        stubbedGetExtendCfgModule.returns(extendsObj);
+
+        const expectedConfigObj = {
+          'version-type': 'off'
+        };
+        const result = config.get();
+
+        spy.calledOnce.should.be.true();
+        spy.firstCall.calledWithExactly(__dirname, passedConfig);
+        result.should.eql(expectedConfigObj);
+
+        config._getExtendsConfigModule.restore();
+      });
     });
   });
 
-  context('when a rule is set to a boolean', function() {
-    it('an error should be thrown', function() {
-      const rcFileObj = {
-        'require-author': true,
-        'require-version': 'warning',
-        'valid-values-author': ['error', ['Thomas', 'Lindner', 'Thomas Lindner']],
-        'valid-values-private': ['warning', [true, false]]
-      };
-      const config = new Config(rcFileObj);
+  context('Tests for when a config object is passed', function() {
+    context('when a valid npmpackagejsonlintrc file object is passed', function() {
+      it('the config object should return the parsed JSON as an object', function() {
+        const rcFileObj = {
+          rules: {
+            'require-author': 'error',
+            'require-version': 'warning',
+            'valid-values-author': ['error', ['Thomas', 'Lindner', 'Thomas Lindner']],
+            'valid-values-private': ['warning', [true, false]]
+          }
+        };
+        const config = new Config(rcFileObj);
+        const expectedConfigObj = {
+          'require-author': 'error',
+          'require-version': 'warning',
+          'valid-values-author': ['error', ['Thomas', 'Lindner', 'Thomas Lindner']],
+          'valid-values-private': ['warning', [true, false]]
+        };
 
-      (function() {
-        config._validateConfig(rcFileObj);
-      }).should.throw('require-author - must be set to "error" or "warning". Currently set to true');
+        config.get().should.eql(expectedConfigObj);
+      });
     });
   });
 
-  context('when a rule is set to a number', function() {
-    it('an error should be thrown', function() {
-      const rcFileObj = {
-        'require-author': 1,
-        'require-version': 'warning',
-        'valid-values-author': ['error', ['Thomas', 'Lindner', 'Thomas Lindner']],
-        'valid-values-private': ['warning', [true, false]]
-      };
-      const config = new Config(rcFileObj);
+  context('_validateConfig tests', function() {
+    context('when a invalid config object is passed', function() {
+      it('an error should be thrown', function() {
+        const rcFileObj = {
+          'require-author': 'error',
+          'require-version': 'warning',
+          'valid-values-author': ['error', ['Thomas', 'Lindner', 'Thomas Lindner']],
+          'valid-values-private': ['warning', [true, false]]
+        };
+        const config = new Config(rcFileObj);
 
-      (function() {
-        config._validateConfig(rcFileObj);
-      }).should.throw('require-author - must be set to "error" or "warning". Currently set to 1');
+        (function() {
+          config._validateConfig(rcFileObj);
+        }).should.throw('`rules` object missing in config');
+      });
     });
   });
 
-  context('when a rule is an array rule and the first key is not equal to error or warning', function() {
-    it('an error should be thrown', function() {
-      const rcFileObj = {
-        'require-author': 'error',
-        'require-version': 'warning',
-        'valid-values-author': [true, ['Thomas', 'Lindner', 'Thomas Lindner']],
-        'valid-values-private': ['warning', [true, false]]
-      };
-      const config = new Config(rcFileObj);
+  context('_validateRulesConfig tests', function() {
+    context('when a valid npmpackagejsonlintrc file object is passed', function() {
+      it('true should be returned', function() {
+        const rcFileObj = {
+          'require-author': 'error',
+          'require-version': 'warning',
+          'valid-values-author': ['error', ['Thomas', 'Lindner', 'Thomas Lindner']],
+          'valid-values-private': ['warning', [true, false]]
+        };
+        const config = new Config(rcFileObj);
 
-      (function() {
-        config._validateConfig(rcFileObj);
-      }).should.throw('valid-values-author - first key must be set to "error" or "warning". Currently set to true');
+        config._validateRulesConfig(rcFileObj).should.eql(true);
+      });
     });
-  });
 
-  context('when a rule is an array rule and the second key is not an Array', function() {
-    it('an error should be thrown', function() {
-      const rcFileObj = {
-        'require-author': 'error',
-        'require-version': 'warning',
-        'valid-values-author': ['error', 'Thomas'],
-        'valid-values-private': ['warning', [true, false]]
-      };
-      const config = new Config(rcFileObj);
+    context('when a valid npmpackagejsonlintrc file object is passed', function() {
+      it('true should be returned', function() {
+        const rcFileObj = {
+          'require-author': 'error',
+          'require-version': 'warning',
+          'valid-values-author': ['error', ['Thomas', 'Lindner', 'Thomas Lindner']],
+          'valid-values-private': ['warning', [true, false]]
+        };
+        const config = new Config(rcFileObj);
 
-      (function() {
-        config._validateConfig(rcFileObj);
-      }).should.throw('valid-values-author - second key must be set an array. Currently set to Thomas');
+        config._validateRulesConfig(rcFileObj).should.eql(true);
+      });
+    });
+
+    context('when a rule is set to a boolean', function() {
+      it('an error should be thrown', function() {
+        const rcFileObj = {
+          'require-author': true,
+          'require-version': 'warning',
+          'valid-values-author': ['error', ['Thomas', 'Lindner', 'Thomas Lindner']],
+          'valid-values-private': ['warning', [true, false]]
+        };
+        const config = new Config(rcFileObj);
+
+        (function() {
+          config._validateRulesConfig(rcFileObj);
+        }).should.throw('require-author - must be set to "error", "warning", or "off". Currently set to true');
+      });
+    });
+
+    context('when a rule is set to a number', function() {
+      it('an error should be thrown', function() {
+        const rcFileObj = {
+          'require-author': 1,
+          'require-version': 'warning',
+          'valid-values-author': ['error', ['Thomas', 'Lindner', 'Thomas Lindner']],
+          'valid-values-private': ['warning', [true, false]]
+        };
+        const config = new Config(rcFileObj);
+
+        (function() {
+          config._validateRulesConfig(rcFileObj);
+        }).should.throw('require-author - must be set to "error", "warning", or "off". Currently set to 1');
+      });
+    });
+
+    context('when a rule is an array rule and the first key is not equal to error, warning, or off', function() {
+      it('an error should be thrown', function() {
+        const rcFileObj = {
+          'require-author': 'error',
+          'require-version': 'warning',
+          'valid-values-author': [true, ['Thomas', 'Lindner', 'Thomas Lindner']],
+          'valid-values-private': ['warning', [true, false]]
+        };
+        const config = new Config(rcFileObj);
+
+        (function() {
+          config._validateRulesConfig(rcFileObj);
+        }).should.throw('valid-values-author - first key must be set to "error", "warning", or "off". Currently set to true');
+      });
+    });
+
+    context('when a rule is an array rule and the second key is not an Array', function() {
+      it('an error should be thrown', function() {
+        const rcFileObj = {
+          'require-author': 'error',
+          'require-version': 'warning',
+          'valid-values-author': ['error', 'Thomas'],
+          'valid-values-private': ['warning', [true, false]]
+        };
+        const config = new Config(rcFileObj);
+
+        (function() {
+          config._validateRulesConfig(rcFileObj);
+        }).should.throw('valid-values-author - second key must be set an array. Currently set to Thomas');
+      });
     });
   });
 });
diff --git a/tests/unit/NpmPackageJsonLintTest.js b/tests/unit/NpmPackageJsonLintTest.js
index 7560463..c49874f 100755
--- a/tests/unit/NpmPackageJsonLintTest.js
+++ b/tests/unit/NpmPackageJsonLintTest.js
@@ -24,11 +24,6 @@ describe('NpmPackageJsonLint Unit Tests', function() {
           ignoreWarnings: false
         };
         const npmPackageJsonLint = new NpmPackageJsonLint(packageJsonData, config, options);
-        const rules = {
-          'description-type': 'rules\\description-type.js',
-          'name-format': 'rules\\name-format.js'
-        };
-        const rulesStub = sinon.stub(npmPackageJsonLint, '_loadRules').returns(rules);
         const configStub = sinon.stub(npmPackageJsonLint, '_getConfig').returns(config);
         const response = npmPackageJsonLint.lint();
         const expectedErrorCount = 2;
@@ -52,11 +47,6 @@ describe('NpmPackageJsonLint Unit Tests', function() {
           ignoreWarnings: false
         };
         const npmPackageJsonLint = new NpmPackageJsonLint(packageJsonData, config, options);
-        const rules = {
-          'require-keywords': 'rules\\require-keywords.js',
-          'name-format': 'rules\\name-format.js'
-        };
-        const rulesStub = sinon.stub(npmPackageJsonLint, '_loadRules').returns(rules);
         const configStub = sinon.stub(npmPackageJsonLint, '_getConfig').returns(config);
         const response = npmPackageJsonLint.lint();
         const expectedErrorCount = 1;
@@ -67,6 +57,29 @@ describe('NpmPackageJsonLint Unit Tests', function() {
       });
     });
 
+    context('validate that errors and warnings are set, but "off" rules are skipped!', function() {
+      it('zero errors and zero warnings expected', function() {
+        const packageJsonData = {
+          name: 'ALLCAPS'
+        };
+        const config = {
+          'require-keywords': 'off',
+          'name-format': 'off'
+        };
+        const options = {
+          ignoreWarnings: false
+        };
+        const npmPackageJsonLint = new NpmPackageJsonLint(packageJsonData, config, options);
+        const configStub = sinon.stub(npmPackageJsonLint, '_getConfig').returns(config);
+        const response = npmPackageJsonLint.lint();
+        const expectedErrorCount = 0;
+        const expectedWarningCount = 0;
+
+        response.errors.length.should.equal(expectedErrorCount);
+        response.warnings.length.should.equal(expectedWarningCount);
+      });
+    });
+
     context('validate that errors and warnings are set', function() {
       it('one error and one warning expected', function() {
         const packageJsonData = {
@@ -80,11 +93,6 @@ describe('NpmPackageJsonLint Unit Tests', function() {
           ignoreWarnings: true
         };
         const npmPackageJsonLint = new NpmPackageJsonLint(packageJsonData, config, options);
-        const rules = {
-          'require-keywords': 'rules\\require-keywords.js',
-          'name-format': 'rules\\name-format.js'
-        };
-        const rulesStub = sinon.stub(npmPackageJsonLint, '_loadRules').returns(rules);
         const configStub = sinon.stub(npmPackageJsonLint, '_getConfig').returns(config);
         const response = npmPackageJsonLint.lint();
         const expectedErrorCount = 1;
@@ -109,10 +117,6 @@ describe('NpmPackageJsonLint Unit Tests', function() {
           ignoreWarnings: true
         };
         const npmPackageJsonLint = new NpmPackageJsonLint(packageJsonData, config, options);
-        const rules = {
-          'valid-values-author': 'rules\\valid-values-author.js'
-        };
-        const rulesStub = sinon.stub(npmPackageJsonLint, '_loadRules').returns(rules);
         const configStub = sinon.stub(npmPackageJsonLint, '_getConfig').returns(config);
         const response = npmPackageJsonLint.lint();
         const expectedErrorCount = 1;
@@ -122,4 +126,29 @@ describe('NpmPackageJsonLint Unit Tests', function() {
       });
     });
   });
+
+  describe('_getConfig method', function() {
+    context('when a valid config object is passed', function() {
+      it('config is returned', function() {
+        const packageJsonData = {
+          author: 'Caitlin Snow'
+        };
+        const config = {
+          rules: {
+            'require-author': 'error'
+          }
+        };
+        const options = {
+          ignoreWarnings: true
+        };
+        const npmPackageJsonLint = new NpmPackageJsonLint(packageJsonData, config, options);
+        const actual = npmPackageJsonLint._getConfig(config);
+        const expected = {
+          'require-author': 'error'
+        };
+
+        actual.should.eql(expected);
+      });
+    });
+  });
 });