Repository: tclindner/npm-package-json-lint
Pull request #12 (2016-05-13 20:48:12)

No version zero rules
Add two new rules for checking whether dependencies are version 0


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9b50973..59657ff 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
 - New rule: prefer-tilde-version-dependencies
 - New rule: prefer-caret-version-devDependencies
 - New rule: prefer-tilde-version-devDependencies
+- New rule: prefer-no-version-zero-dependencies
+- New rule: prefer-no-version-zero-devDependencies
 
 ### Changed
 
diff --git a/src/rules/prefer-no-version-zero-dependencies.js b/src/rules/prefer-no-version-zero-dependencies.js
new file mode 100644
index 0000000..bfb2b36
--- /dev/null
+++ b/src/rules/prefer-no-version-zero-dependencies.js
@@ -0,0 +1,21 @@
+"use strict";
+
+const hasDepVersZero = require("./../validators/dependency-audit").hasDepVersZero;
+const LintIssue = require("./../LintIssue");
+const lintId = "prefer-no-version-zero-dependencies";
+const lintType = "error";
+const nodeName = "dependencies";
+const message = "You have invalid version 0 dependencies. Please use modules with a major version >= 1.";
+const ruleType = "no-version-zero";
+
+const lint = function(packageJsonData) {
+  if (hasDepVersZero(packageJsonData, nodeName)) {
+    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-no-version-zero-devDependencies.js b/src/rules/prefer-no-version-zero-devDependencies.js
new file mode 100644
index 0000000..c5728fb
--- /dev/null
+++ b/src/rules/prefer-no-version-zero-devDependencies.js
@@ -0,0 +1,21 @@
+"use strict";
+
+const hasDepVersZero = require("./../validators/dependency-audit").hasDepVersZero;
+const LintIssue = require("./../LintIssue");
+const lintId = "prefer-no-version-zero-devDependencies";
+const lintType = "error";
+const nodeName = "devDependencies";
+const message = "You have invalid version 0 dependencies. Please use modules with a major version >= 1.";
+const ruleType = "no-version-zero";
+
+const lint = function(packageJsonData) {
+  if (hasDepVersZero(packageJsonData, nodeName)) {
+    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 0d56503..527322a 100755
--- a/src/validators/dependency-audit.js
+++ b/src/validators/dependency-audit.js
@@ -49,6 +49,36 @@ const hasDepPrereleaseVers = function(packageJsonData, nodeName, depsToCheckFor)
   return false;
 };
 
+/**
+ * Determines whether or not the package has a dependency with a major version of 0
+ * @param  {object} packageJsonData         Valid JSON
+ * @param  {string} nodeName                Name of a node in the package.json file
+ * @return {boolean}                        True if the package has a dependency with version 0. False if it does not or the node is missing.
+ */
+const hasDepVersZero = function(packageJsonData, nodeName) {
+  if (!packageJsonData.hasOwnProperty(nodeName)) {
+    return false;
+  }
+
+  for (const dependencyName in packageJsonData[nodeName]) {
+    const dependencyVersRange = packageJsonData[nodeName][dependencyName];
+
+    if (semver.validRange(dependencyVersRange)) {
+      const startIndex = 0;
+      const length = 1;
+      const dependencyVersion = dependencyVersRange.replace(/[\D]+/g, "");
+      const dependencyMjrVersion = dependencyVersion.substr(startIndex, length);
+
+      // if first char is 0 then major version is 0
+      if (dependencyMjrVersion === "0") {
+        return true;
+      }
+    }
+  }
+
+  return false;
+};
+
 /**
  * Determines whether or not all dependency version ranges match expected range
  * @param  {object} packageJsonData         Valid JSON
@@ -77,4 +107,5 @@ const areVersRangesValid = function(packageJsonData, nodeName, rangeSpecifier) {
 
 module.exports.hasDependency = hasDependency;
 module.exports.hasDepPrereleaseVers = hasDepPrereleaseVers;
+module.exports.hasDepVersZero = hasDepVersZero;
 module.exports.areVersRangesValid = areVersRangesValid;


Test patch:
diff --git a/tests/unit/rules/prefer-no-version-zero-dependenciesTest.js b/tests/unit/rules/prefer-no-version-zero-dependenciesTest.js
new file mode 100644
index 0000000..ee68f96
--- /dev/null
+++ b/tests/unit/rules/prefer-no-version-zero-dependenciesTest.js
@@ -0,0 +1,45 @@
+"use strict";
+
+const should = require("should");
+const requireHelper = require("../../require_helper");
+const lint = requireHelper("rules/prefer-no-version-zero-dependencies").lint;
+
+describe("prefer-no-version-zero-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": "~0.3.0"
+        }
+      };
+      const response = lint(packageJsonData);
+
+      response.lintId.should.equal("prefer-no-version-zero-dependencies");
+      response.lintType.should.equal("error");
+      response.node.should.equal("dependencies");
+      response.lintMessage.should.equal("You have invalid version 0 dependencies. Please use modules with a major version >= 1.");
+    });
+  });
+
+  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-no-version-zero-devDependenciesTest.js b/tests/unit/rules/prefer-no-version-zero-devDependenciesTest.js
new file mode 100644
index 0000000..453ad2f
--- /dev/null
+++ b/tests/unit/rules/prefer-no-version-zero-devDependenciesTest.js
@@ -0,0 +1,45 @@
+"use strict";
+
+const should = require("should");
+const requireHelper = require("../../require_helper");
+const lint = requireHelper("rules/prefer-no-version-zero-devDependencies").lint;
+
+describe("prefer-no-version-zero-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": "~0.3.0"
+        }
+      };
+      const response = lint(packageJsonData);
+
+      response.lintId.should.equal("prefer-no-version-zero-devDependencies");
+      response.lintType.should.equal("error");
+      response.node.should.equal("devDependencies");
+      response.lintMessage.should.equal("You have invalid version 0 dependencies. Please use modules with a major version >= 1.");
+    });
+  });
+
+  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 9116c18..2ff9ed3 100755
--- a/tests/unit/validators/dependency-auditTest.js
+++ b/tests/unit/validators/dependency-auditTest.js
@@ -81,6 +81,356 @@ describe("dependency-audit Unit Tests", function() {
     });
   });
 
+  describe("hasDepVersZero method - part 1", function() {
+    context("when the node doesn't exist in the package.json file", function() {
+      it("false should be returned", function() {
+        const packageJson = {
+          dependencies: {
+            "npm-package-json-lint": "~ 1.0"
+          }
+        };
+        const response = dependencyAudit.hasDepVersZero(packageJson, "devDependencies");
+
+        response.should.be.false();
+      });
+    });
+
+    context("when the node exists in the package.json file, not all versions are 1+", function() {
+      it("true should be returned", function() {
+        const packageJson = {
+          dependencies: {
+            "npm-package-json-lint": "~0.6.1-1"
+          }
+        };
+        const response = dependencyAudit.hasDepVersZero(packageJson, "dependencies");
+
+        response.should.be.true();
+      });
+    });
+
+    context("when the node exists in the package.json file, not all versions are 1+", function() {
+      it("true should be returned", function() {
+        const packageJson = {
+          dependencies: {
+            "npm-package-json-lint": ">=0.1.97"
+          }
+        };
+        const response = dependencyAudit.hasDepVersZero(packageJson, "dependencies");
+
+        response.should.be.true();
+      });
+    });
+
+    context("when the node exists in the package.json file, not all versions are 1+", function() {
+      it("true should be returned", function() {
+        const packageJson = {
+          dependencies: {
+            "npm-package-json-lint": "0.1.20 || 1.2.4"
+          }
+        };
+        const response = dependencyAudit.hasDepVersZero(packageJson, "dependencies");
+
+        response.should.be.true();
+      });
+    });
+
+    context("when the node exists in the package.json file, not all versions are 1+", function() {
+      it("true should be returned", function() {
+        const packageJson = {
+          dependencies: {
+            "npm-package-json-lint": "0.1.20 || >1.2.4"
+          }
+        };
+        const response = dependencyAudit.hasDepVersZero(packageJson, "dependencies");
+
+        response.should.be.true();
+      });
+    });
+
+    context("when the node exists in the package.json file, not all versions are 1+", function() {
+      it("true should be returned", function() {
+        const packageJson = {
+          dependencies: {
+            "npm-package-json-lint": "0.1.20 || 1.2.4"
+          }
+        };
+        const response = dependencyAudit.hasDepVersZero(packageJson, "dependencies");
+
+        response.should.be.true();
+      });
+    });
+
+    context("when the node exists in the package.json file, not all versions are 1+", function() {
+      it("true should be returned", function() {
+        const packageJson = {
+          dependencies: {
+            "npm-package-json-lint": "0.1.20 || 1.2.4"
+          }
+        };
+        const response = dependencyAudit.hasDepVersZero(packageJson, "dependencies");
+
+        response.should.be.true();
+      });
+    });
+
+    context("when the node exists in the package.json file, not all versions are 1+", function() {
+      it("true should be returned", function() {
+        const packageJson = {
+          dependencies: {
+            "npm-package-json-lint": ">=0.2.3 || <0.0.1"
+          }
+        };
+        const response = dependencyAudit.hasDepVersZero(packageJson, "dependencies");
+
+        response.should.be.true();
+      });
+    });
+
+    context("when the node exists in the package.json file, not all versions are 1+", function() {
+      it("true should be returned", function() {
+        const packageJson = {
+          dependencies: {
+            "npm-package-json-lint": ">=0.2.3 || <0.0.1"
+          }
+        };
+        const response = dependencyAudit.hasDepVersZero(packageJson, "dependencies");
+
+        response.should.be.true();
+      });
+    });
+
+    context("when the node exists in the package.json file, not all versions are 1+", function() {
+      it("true should be returned", function() {
+        const packageJson = {
+          dependencies: {
+            "npm-package-json-lint": ">=0.2.3 || <0.0.1"
+          }
+        };
+        const response = dependencyAudit.hasDepVersZero(packageJson, "dependencies");
+
+        response.should.be.true();
+      });
+    });
+
+    context("when the node exists in the package.json file, not all versions are 1+", function() {
+      it("true should be returned", function() {
+        const packageJson = {
+          dependencies: {
+            "npm-package-json-lint": "~v0.5.4-pre"
+          }
+        };
+        const response = dependencyAudit.hasDepVersZero(packageJson, "dependencies");
+
+        response.should.be.true();
+      });
+    });
+  });
+
+  describe("hasDepVersZero method - part 2", function() {
+    context("when the node exists in the package.json file, not all versions are 1+", function() {
+      it("true should be returned", function() {
+        const packageJson = {
+          dependencies: {
+            "npm-package-json-lint": "=0.7.x"
+          }
+        };
+        const response = dependencyAudit.hasDepVersZero(packageJson, "dependencies");
+
+        response.should.be.true();
+      });
+    });
+
+    context("when the node exists in the package.json file, not all versions are 1+", function() {
+      it("true should be returned", function() {
+        const packageJson = {
+          dependencies: {
+            "npm-package-json-lint": ">=0.7.x"
+          }
+        };
+        const response = dependencyAudit.hasDepVersZero(packageJson, "dependencies");
+
+        response.should.be.true();
+      });
+    });
+
+    context("when the node exists in the package.json file, not all versions are 1+", function() {
+      it("true should be returned", function() {
+        const packageJson = {
+          dependencies: {
+            "npm-package-json-lint": "<=0.7.x"
+          }
+        };
+        const response = dependencyAudit.hasDepVersZero(packageJson, "dependencies");
+
+        response.should.be.true();
+      });
+    });
+
+    context("when the node exists in the package.json file, not all versions are 1+", function() {
+      it("true should be returned", function() {
+        const packageJson = {
+          dependencies: {
+            "npm-package-json-lint": ">0.2.3 >0.2.4 <=0.2.5"
+          }
+        };
+        const response = dependencyAudit.hasDepVersZero(packageJson, "dependencies");
+
+        response.should.be.true();
+      });
+    });
+
+    context("when the node exists in the package.json file, not all versions are 1+", function() {
+      it("true should be returned", function() {
+        const packageJson = {
+          dependencies: {
+            "npm-package-json-lint": ">=0.2.3 <=0.2.4"
+          }
+        };
+        const response = dependencyAudit.hasDepVersZero(packageJson, "dependencies");
+
+        response.should.be.true();
+      });
+    });
+
+    context("when the node exists in the package.json file, not all versions are 1+", function() {
+      it("true should be returned", function() {
+        const packageJson = {
+          dependencies: {
+            "npm-package-json-lint": "^0.1.0 || ~3.0.1 || 5.0.0"
+          }
+        };
+        const response = dependencyAudit.hasDepVersZero(packageJson, "dependencies");
+
+        response.should.be.true();
+      });
+    });
+
+    context("when the node exists in the package.json file, not all versions are 1+", function() {
+      it("true should be returned", function() {
+        const packageJson = {
+          dependencies: {
+            "npm-package-json-lint": "^0.1.0 || ~3.0.1 || 5.0.0"
+          }
+        };
+        const response = dependencyAudit.hasDepVersZero(packageJson, "dependencies");
+
+        response.should.be.true();
+      });
+    });
+
+    context("when the node exists in the package.json file, not all versions are 1+", function() {
+      it("true should be returned", function() {
+        const packageJson = {
+          dependencies: {
+            "npm-package-json-lint": "^0.1.0 || ~3.0.1 || 5.0.0"
+          }
+        };
+        const response = dependencyAudit.hasDepVersZero(packageJson, "dependencies");
+
+        response.should.be.true();
+      });
+    });
+
+    context("when the node exists in the package.json file, not all versions are 1+", function() {
+      it("true should be returned", function() {
+        const packageJson = {
+          dependencies: {
+            "npm-package-json-lint": "^0.1.0 || ~3.0.1 || >4 <=5.0.0"
+          }
+        };
+        const response = dependencyAudit.hasDepVersZero(packageJson, "dependencies");
+
+        response.should.be.true();
+      });
+    });
+
+    context("when the node exists in the package.json file, not all versions are 1+", function() {
+      it("false should be returned", function() {
+        const packageJson = {
+          dependencies: {
+            "npm-package-json-lint": "=0.1."
+          }
+        };
+        const response = dependencyAudit.hasDepVersZero(packageJson, "dependencies");
+
+        response.should.be.false();
+      });
+    });
+
+    context("when the node exists in the package.json file, all versions are 1+", function() {
+      it("true should be returned", function() {
+        const packageJson = {
+          dependencies: {
+            "npm-package-json-lint": "~ 1.0",
+            "npm-package-json-lint3": "1.0.0 - 2.0.0",
+            "npm-package-json-lint4": "1.0.0 - 2.0.0",
+            "npm-package-json-lint5": "1.0.0",
+            "npm-package-json-lint6": ">=*",
+            "npm-package-json-lint8": "*",
+            "npm-package-json-lint9": ">=1.0.0",
+            "npm-package-json-lint10": ">=1.0.0",
+            "npm-package-json-lint11": ">=1.0.0",
+            "npm-package-json-lint12": ">1.0.0",
+            "npm-package-json-lint13": ">1.0.0",
+            "npm-package-json-lint14": "<=2.0.0",
+            "npm-package-json-lint15": "<=2.0.0",
+            "npm-package-json-lint16": "<=2.0.0",
+            "npm-package-json-lint17": "<2.0.0",
+            "npm-package-json-lint18": "<2.0.0",
+            "npm-package-json-lint19": ">= 1.0.0",
+            "npm-package-json-lint20": ">=  1.0.0",
+            "npm-package-json-lint21": ">=   1.0.0",
+            "npm-package-json-lint22": "> 1.0.0",
+            "npm-package-json-lint23": ">  1.0.0",
+            "npm-package-json-lint24": "<=   2.0.0",
+            "npm-package-json-lint25": "<= 2.0.0",
+            "npm-package-json-lint26": "<=  2.0.0",
+            "npm-package-json-lint27": "<    2.0.0",
+            "npm-package-json-lint28": "<\t2.0.0",
+            "npm-package-json-lint38": "||",
+            "npm-package-json-lint39": "2.x.x",
+            "npm-package-json-lint40": "1.2.x",
+            "npm-package-json-lint41": "1.2.x || 2.x",
+            "npm-package-json-lint42": "1.2.x || 2.x",
+            "npm-package-json-lint43": "x",
+            "npm-package-json-lint44": "2.*.*",
+            "npm-package-json-lint45": "1.2.*",
+            "npm-package-json-lint46": "1.2.* || 2.*",
+            "npm-package-json-lint47": "1.2.* || 2.*",
+            "npm-package-json-lint48": "1.2.* || 2.*",
+            "npm-package-json-lint49": "*",
+            "npm-package-json-lint50": "2",
+            "npm-package-json-lint51": "2.3",
+            "npm-package-json-lint52": "~2.4",
+            "npm-package-json-lint53": "~2.4",
+            "npm-package-json-lint54": "~>3.2.1",
+            "npm-package-json-lint55": "~1",
+            "npm-package-json-lint56": "~>1",
+            "npm-package-json-lint57": "~> 1",
+            "npm-package-json-lint58": "~1.0",
+            "npm-package-json-lint59": "~ 1.0",
+            "npm-package-json-lint60": ">=1",
+            "npm-package-json-lint61": ">= 1",
+            "npm-package-json-lint62": "<1.2",
+            "npm-package-json-lint63": "< 1.2",
+            "npm-package-json-lint71": "1.0.0 - 2.0.0",
+            "npm-package-json-lint72": "^3.0.0",
+            "npm-package-json-lint73": "^1.0.0 || ~2.0.1",
+            "npm-package-json-lint78": "^1.0.0alpha",
+            "npm-package-json-lint79": "~1.0.0alpha",
+            "npm-package-json-lint80": "^1.0.0-alpha",
+            "npm-package-json-lint81": "~1.0.0-alpha",
+            "npm-package-json-lint82": "^1.0.0-alpha",
+            "npm-package-json-lint83": "~1.0.0-alpha"
+          }
+        };
+        const response = dependencyAudit.hasDepVersZero(packageJson, "dependencies");
+
+        response.should.be.false();
+      });
+    });
+  });
+
   describe("areVersRangesValid method", function() {
     context("when the node doesn't exist in the package.json file", function() {
       it("true should be returned", function() {