Repository: tclindner/npm-package-json-lint
Pull request #11 (2016-05-13 16:08:42)

Add four new rules
- New rule: prefer-caret-version-dependencies
- New rule: prefer-tilde-version-dependencies
- New rule: prefer-caret-version-devDependencies
- New rule: prefer-tilde-version-devDependencies


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 41112d9..9b50973 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,10 @@ This project adheres to [Semantic Versioning](http://semver.org/).
 
 ## [Unreleased]
 ### Added
+- New rule: prefer-caret-version-dependencies
+- New rule: prefer-tilde-version-dependencies
+- New rule: prefer-caret-version-devDependencies
+- New rule: prefer-tilde-version-devDependencies
 
 ### Changed
 
diff --git a/README.md b/README.md
index 2aa574e..4465ae3 100755
--- a/README.md
+++ b/README.md
@@ -71,7 +71,7 @@ npm-package-json-lint has a configurable set of rules. Please see the [wiki](htt
   4. Message - example: author is required
   5. 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 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), to run a set of rules. If neither of the options above are specified then package json lint looks for a global [`.npmpackagejsonlintrc`](https://github.com/tclindner/npm-package-json-lint/wiki/npm-package-json-lint) 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) file doesn't exist then all rules are enabled by [default](src/defaultConfig.js).
+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 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 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 all rules are enabled by [default](src/defaultConfig.js).
 
 ## Contributing
 
diff --git a/src/rules/prefer-caret-version-dependencies.js b/src/rules/prefer-caret-version-dependencies.js
new file mode 100644
index 0000000..e634d77
--- /dev/null
+++ b/src/rules/prefer-caret-version-dependencies.js
@@ -0,0 +1,23 @@
+"use strict";
+
+const areVersRangesValid = require("./../validators/dependency-audit").areVersRangesValid;
+const LintIssue = require("./../LintIssue");
+const lintId = "prefer-caret-version-dependencies";
+const lintType = "error";
+const nodeName = "dependencies";
+const message = "You are using an invalid version range. Please use ^.";
+const ruleType = "dependencies-version-range";
+
+const lint = function(packageJsonData) {
+  const rangeSpecifier = "^";
+
+  if (!areVersRangesValid(packageJsonData, nodeName, rangeSpecifier)) {
+    return new LintIssue(lintId, lintType, nodeName, message);
+  }
+
+  return true;
+};
+
+module.exports.lint = lint;
+module.exports.lintType = lintType;
+module.exports.ruleType = ruleType;
diff --git a/src/rules/prefer-caret-version-devDependencies.js b/src/rules/prefer-caret-version-devDependencies.js
new file mode 100644
index 0000000..ccd8254
--- /dev/null
+++ b/src/rules/prefer-caret-version-devDependencies.js
@@ -0,0 +1,23 @@
+"use strict";
+
+const areVersRangesValid = require("./../validators/dependency-audit").areVersRangesValid;
+const LintIssue = require("./../LintIssue");
+const lintId = "prefer-caret-version-devDependencies";
+const lintType = "error";
+const nodeName = "devDependencies";
+const message = "You are using an invalid version range. Please use ^.";
+const ruleType = "devDependencies-version-range";
+
+const lint = function(packageJsonData) {
+  const rangeSpecifier = "^";
+
+  if (!areVersRangesValid(packageJsonData, nodeName, rangeSpecifier)) {
+    return new LintIssue(lintId, lintType, nodeName, message);
+  }
+
+  return true;
+};
+
+module.exports.lint = lint;
+module.exports.lintType = lintType;
+module.exports.ruleType = ruleType;
diff --git a/src/rules/prefer-tilde-version-dependencies.js b/src/rules/prefer-tilde-version-dependencies.js
new file mode 100644
index 0000000..ece054c
--- /dev/null
+++ b/src/rules/prefer-tilde-version-dependencies.js
@@ -0,0 +1,23 @@
+"use strict";
+
+const areVersRangesValid = require("./../validators/dependency-audit").areVersRangesValid;
+const LintIssue = require("./../LintIssue");
+const lintId = "prefer-tilde-version-dependencies";
+const lintType = "error";
+const nodeName = "dependencies";
+const message = "You are using an invalid version range. Please use ~.";
+const ruleType = "dependencies-version-range";
+
+const lint = function(packageJsonData) {
+  const rangeSpecifier = "~";
+
+  if (!areVersRangesValid(packageJsonData, nodeName, rangeSpecifier)) {
+    return new LintIssue(lintId, lintType, nodeName, message);
+  }
+
+  return true;
+};
+
+module.exports.lint = lint;
+module.exports.lintType = lintType;
+module.exports.ruleType = ruleType;
diff --git a/src/rules/prefer-tilde-version-devDependencies.js b/src/rules/prefer-tilde-version-devDependencies.js
new file mode 100644
index 0000000..8ee6d3c
--- /dev/null
+++ b/src/rules/prefer-tilde-version-devDependencies.js
@@ -0,0 +1,23 @@
+"use strict";
+
+const areVersRangesValid = require("./../validators/dependency-audit").areVersRangesValid;
+const LintIssue = require("./../LintIssue");
+const lintId = "prefer-tilde-version-devDependencies";
+const lintType = "error";
+const nodeName = "devDependencies";
+const message = "You are using an invalid version range. Please use ~.";
+const ruleType = "devDependencies-version-range";
+
+const lint = function(packageJsonData) {
+  const rangeSpecifier = "~";
+
+  if (!areVersRangesValid(packageJsonData, nodeName, rangeSpecifier)) {
+    return new LintIssue(lintId, lintType, nodeName, message);
+  }
+
+  return true;
+};
+
+module.exports.lint = lint;
+module.exports.lintType = lintType;
+module.exports.ruleType = ruleType;
diff --git a/src/validators/dependency-audit.js b/src/validators/dependency-audit.js
index 1c0657e..0d56503 100755
--- a/src/validators/dependency-audit.js
+++ b/src/validators/dependency-audit.js
@@ -49,5 +49,32 @@ const hasDepPrereleaseVers = function(packageJsonData, nodeName, depsToCheckFor)
   return false;
 };
 
+/**
+ * Determines whether or not all dependency version ranges match expected range
+ * @param  {object} packageJsonData         Valid JSON
+ * @param  {string} nodeName                Name of a node in the package.json file
+ * @param  {string} rangeSpecifier          A version range specifier
+ * @return {boolean}                        False if the package has an invalid range. True if it is not or the node is missing.
+ */
+const areVersRangesValid = function(packageJsonData, nodeName, rangeSpecifier) {
+  if (!packageJsonData.hasOwnProperty(nodeName)) {
+    return true;
+  }
+
+  const firstCharOfStr = 0;
+  let rangesValid = true;
+
+  for (const dependencyName in packageJsonData[nodeName]) {
+    const dependencyVersion = packageJsonData[nodeName][dependencyName];
+
+    if (!dependencyVersion.startsWith(rangeSpecifier, firstCharOfStr)) {
+      rangesValid = false;
+    }
+  }
+
+  return rangesValid;
+};
+
 module.exports.hasDependency = hasDependency;
 module.exports.hasDepPrereleaseVers = hasDepPrereleaseVers;
+module.exports.areVersRangesValid = areVersRangesValid;


Test patch:
diff --git a/tests/unit/rules/prefer-caret-version-dependenciesTest.js b/tests/unit/rules/prefer-caret-version-dependenciesTest.js
new file mode 100644
index 0000000..7c5499a
--- /dev/null
+++ b/tests/unit/rules/prefer-caret-version-dependenciesTest.js
@@ -0,0 +1,45 @@
+"use strict";
+
+const should = require("should");
+const requireHelper = require("../../require_helper");
+const lint = requireHelper("rules/prefer-caret-version-dependencies").lint;
+
+describe("prefer-caret-version-dependencies Unit Tests", function() {
+  context("when package.json has node with an invalid value", function() {
+    it("LintIssue object should be returned", function() {
+      const packageJsonData = {
+        dependencies: {
+          "npm-package-json-lint": "~1.0.0"
+        }
+      };
+      const response = lint(packageJsonData);
+
+      response.lintId.should.equal("prefer-caret-version-dependencies");
+      response.lintType.should.equal("error");
+      response.node.should.equal("dependencies");
+      response.lintMessage.should.equal("You are using an invalid version range. Please use ^.");
+    });
+  });
+
+  context("when package.json has node with a valid value", function() {
+    it("LintIssue object should be returned", function() {
+      const packageJsonData = {
+        dependencies: {
+          "gulp-npm-package-json-lint": "^1.0.0"
+        }
+      };
+      const response = lint(packageJsonData);
+
+      response.should.be.true();
+    });
+  });
+
+  context("when package.json does not have node", function() {
+    it("true should be returned", function() {
+      const packageJsonData = {};
+      const response = lint(packageJsonData);
+
+      response.should.be.true();
+    });
+  });
+});
diff --git a/tests/unit/rules/prefer-caret-version-devDependenciesTest.js b/tests/unit/rules/prefer-caret-version-devDependenciesTest.js
new file mode 100644
index 0000000..874d7df
--- /dev/null
+++ b/tests/unit/rules/prefer-caret-version-devDependenciesTest.js
@@ -0,0 +1,45 @@
+"use strict";
+
+const should = require("should");
+const requireHelper = require("../../require_helper");
+const lint = requireHelper("rules/prefer-caret-version-devDependencies").lint;
+
+describe("prefer-caret-version-devDependencies Unit Tests", function() {
+  context("when package.json has node with an invalid value", function() {
+    it("LintIssue object should be returned", function() {
+      const packageJsonData = {
+        devDependencies: {
+          "npm-package-json-lint": "~1.0.0"
+        }
+      };
+      const response = lint(packageJsonData);
+
+      response.lintId.should.equal("prefer-caret-version-devDependencies");
+      response.lintType.should.equal("error");
+      response.node.should.equal("devDependencies");
+      response.lintMessage.should.equal("You are using an invalid version range. Please use ^.");
+    });
+  });
+
+  context("when package.json has node with a valid value", function() {
+    it("LintIssue object should be returned", function() {
+      const packageJsonData = {
+        devDependencies: {
+          "gulp-npm-package-json-lint": "^1.0.0"
+        }
+      };
+      const response = lint(packageJsonData);
+
+      response.should.be.true();
+    });
+  });
+
+  context("when package.json does not have node", function() {
+    it("true should be returned", function() {
+      const packageJsonData = {};
+      const response = lint(packageJsonData);
+
+      response.should.be.true();
+    });
+  });
+});
diff --git a/tests/unit/rules/prefer-tilde-version-dependenciesTest.js b/tests/unit/rules/prefer-tilde-version-dependenciesTest.js
new file mode 100644
index 0000000..39fc177
--- /dev/null
+++ b/tests/unit/rules/prefer-tilde-version-dependenciesTest.js
@@ -0,0 +1,45 @@
+"use strict";
+
+const should = require("should");
+const requireHelper = require("../../require_helper");
+const lint = requireHelper("rules/prefer-tilde-version-dependencies").lint;
+
+describe("prefer-tilde-version-dependencies Unit Tests", function() {
+  context("when package.json has node with an invalid value", function() {
+    it("LintIssue object should be returned", function() {
+      const packageJsonData = {
+        dependencies: {
+          "npm-package-json-lint": "^1.0.0"
+        }
+      };
+      const response = lint(packageJsonData);
+
+      response.lintId.should.equal("prefer-tilde-version-dependencies");
+      response.lintType.should.equal("error");
+      response.node.should.equal("dependencies");
+      response.lintMessage.should.equal("You are using an invalid version range. Please use ~.");
+    });
+  });
+
+  context("when package.json has node with a valid value", function() {
+    it("LintIssue object should be returned", function() {
+      const packageJsonData = {
+        dependencies: {
+          "gulp-npm-package-json-lint": "~1.0.0"
+        }
+      };
+      const response = lint(packageJsonData);
+
+      response.should.be.true();
+    });
+  });
+
+  context("when package.json does not have node", function() {
+    it("true should be returned", function() {
+      const packageJsonData = {};
+      const response = lint(packageJsonData);
+
+      response.should.be.true();
+    });
+  });
+});
diff --git a/tests/unit/rules/prefer-tilde-version-devDependenciesTest.js b/tests/unit/rules/prefer-tilde-version-devDependenciesTest.js
new file mode 100644
index 0000000..b9304ae
--- /dev/null
+++ b/tests/unit/rules/prefer-tilde-version-devDependenciesTest.js
@@ -0,0 +1,45 @@
+"use strict";
+
+const should = require("should");
+const requireHelper = require("../../require_helper");
+const lint = requireHelper("rules/prefer-tilde-version-devDependencies").lint;
+
+describe("prefer-tilde-version-devDependencies Unit Tests", function() {
+  context("when package.json has node with an invalid value", function() {
+    it("LintIssue object should be returned", function() {
+      const packageJsonData = {
+        devDependencies: {
+          "npm-package-json-lint": "^1.0.0"
+        }
+      };
+      const response = lint(packageJsonData);
+
+      response.lintId.should.equal("prefer-tilde-version-devDependencies");
+      response.lintType.should.equal("error");
+      response.node.should.equal("devDependencies");
+      response.lintMessage.should.equal("You are using an invalid version range. Please use ~.");
+    });
+  });
+
+  context("when package.json has node with a valid value", function() {
+    it("LintIssue object should be returned", function() {
+      const packageJsonData = {
+        devDependencies: {
+          "gulp-npm-package-json-lint": "~1.0.0"
+        }
+      };
+      const response = lint(packageJsonData);
+
+      response.should.be.true();
+    });
+  });
+
+  context("when package.json does not have node", function() {
+    it("true should be returned", function() {
+      const packageJsonData = {};
+      const response = lint(packageJsonData);
+
+      response.should.be.true();
+    });
+  });
+});
diff --git a/tests/unit/validators/dependency-auditTest.js b/tests/unit/validators/dependency-auditTest.js
index d41463f..9116c18 100755
--- a/tests/unit/validators/dependency-auditTest.js
+++ b/tests/unit/validators/dependency-auditTest.js
@@ -80,4 +80,51 @@ describe("dependency-audit Unit Tests", function() {
       });
     });
   });
+
+  describe("areVersRangesValid method", function() {
+    context("when the node doesn't exist in the package.json file", function() {
+      it("true should be returned", function() {
+        const packageJson = {
+          dependencies: {
+            "npm-package-json-lint": "^1.0.0",
+            "grunt-npm-package-json-lint": "~2.0.0-beta1",
+            "gulp-npm-package-json-lint": "^2.0.0-rc1"
+          }
+        };
+        const response = dependencyAudit.areVersRangesValid(packageJson, "devDependencies", "~");
+
+        response.should.be.true();
+      });
+    });
+
+    context("when the node exists in the package.json file, not all versions are ~", function() {
+      it("false should be returned", function() {
+        const packageJson = {
+          dependencies: {
+            "npm-package-json-lint": "^1.0.0",
+            "grunt-npm-package-json-lint": "~2.0.0-beta1",
+            "gulp-npm-package-json-lint": "^2.0.0-rc1"
+          }
+        };
+        const response = dependencyAudit.areVersRangesValid(packageJson, "dependencies", "~");
+
+        response.should.be.false();
+      });
+    });
+
+    context("when the node exists in the package.json file, all versions are ~", function() {
+      it("true should be returned", function() {
+        const packageJson = {
+          dependencies: {
+            "npm-package-json-lint": "~1.0.0",
+            "grunt-npm-package-json-lint": "~2.0.0-beta1",
+            "gulp-npm-package-json-lint": "~2.0.0-rc1"
+          }
+        };
+        const response = dependencyAudit.areVersRangesValid(packageJson, "dependencies", "~");
+
+        response.should.be.true();
+      });
+    });
+  });
 });