Repository: tclindner/npm-package-json-lint
Pull request #100 (2019-01-28 13:09:57)

feat: `ignorePath` option
**Description of change**

Fixes https://github.com/tclindner/npm-package-json-lint/issues/99

**Checklist**

  - [x] Unit tests have been added
  - [x] Specific notes for documentation, if applicable


tclindner: Hey @evilebottnawi! Thank you so much for opening this PR 🙌 I don't see the `TypeError` you referenced above. It looks like the tests are failing with the following error. I added a comment in the PR about tweaking the path passed to the ignorer. Let me know what you think!
`RangeError: path should be`

I took a peek at master and it looks like it is green. 
<img width="848" alt="image" src="https://user-images.githubusercontent.com/5178550/51954116-946ab100-2404-11e9-9c78-395fdae13404.png">
evilebottnawi: @tclindner hm, i can't reproducible this problem locally, one problem for me is `ypeError: Cannot read property 'equal' of undefined` :confused: And same problem in `master` (reinstall deps doesn't solve problem), investigate
evilebottnawi: It is really weird - `npm test` and `npm coverage` have difference behavior :confused:  
evilebottnawi: Yep, in some cases we have absolute path, let's use `path.relative` to avoid this problem. All green :+1: 

Patch:
diff --git a/README.md b/README.md
index 6c98903..98a2e07 100755
--- a/README.md
+++ b/README.md
@@ -54,6 +54,7 @@ First thing first, let's make sure you have the necessary pre-requisites.
 | npmPkgJsonLint --configFile <file path> | -c | File path to local config file or module name. |
 | npmPkgJsonLint --quiet | -q | Report errors only |
 | npmPkgJsonLint --noConfigFiles | -ncf | Skips loading project config files (i.e. .npmpackagejsonlintrc.json and npmpackagejsonlint.config.js) |
+| npmPkgJsonLint --ignorePath | -i | Path to a file containing patterns that describe files to ignore. |
 
 ### Examples
 
@@ -99,6 +100,12 @@ $ npmPkgJsonLint --quiet ./packages
 
 > Looks for all `package.json` files in the `packages` directory. The CLI engine automatically looks for relevant config files for each package.json file that is found. Removes any warnings from the output using the long form for quieting output.
 
+```bash
+$ npmPkgJsonLint . --ignorePath .gitignore
+```
+
+> Looks for all `package.json` files in the project and exclude ignored paths. The CLI engine automatically looks for relevant config files for each package.json file that is found.
+
 ## Node.js API
 
 npm-package-json-lint exports two main objects: `CLIEngine` and `NpmPackageJsonLint`.
@@ -180,7 +187,8 @@ CLIEngine configuration object
 * `configFile`      {string}  Name of module/file to use.
 * `cwd`             {string}  The current working diretory for all file operations.
 * `useConfigFiles`  {boolean} False disables use of .npmpackagejsonlintrc.json files and npmpackagejsonlint.config.js files.
-* `rules`            {object} An object of rules to use.
+* `ignorePath`      {string} Path to a file containing patterns that describe files to ignore. The path can be absolute or relative to process.cwd(). By default, npm-package-json-lint looks for .npmpackagejsonlintignore in process.cwd().
+* `rules`           {object} An object of rules to use.
 
 ##### Example
 
@@ -193,6 +201,7 @@ const cliEngineOptions = {
   configFile: '',
   cwd: process.cwd(),
   useConfigFiles: true,
+  ignorePath: '',
   rules: {}
 };
 
@@ -220,6 +229,7 @@ const cliEngineOptions = {
   configFile: '',
   cwd: process.cwd(),
   useConfigFiles: true,
+  ignorePath: '',
   rules: {}
 };
 const patterns = ['.'];
@@ -267,6 +277,7 @@ const cliEngineOptions = {
   configFile: '',
   cwd: process.cwd(),
   useConfigFiles: true,
+  ignorePath: '',
   rules: {}
 };
 
diff --git a/package.json b/package.json
index 7aeae5b..5159466 100755
--- a/package.json
+++ b/package.json
@@ -37,6 +37,7 @@
     "ajv": "^6.7.0",
     "chalk": "^2.4.2",
     "glob": "^7.1.3",
+    "ignore": "^5.0.5",
     "is-path-inside": "^2.0.0",
     "is-plain-obj": "^1.1.0",
     "is-resolvable": "^1.1.0",
diff --git a/src/CLIEngine.js b/src/CLIEngine.js
index c6495cb..7f6bbea 100755
--- a/src/CLIEngine.js
+++ b/src/CLIEngine.js
@@ -7,10 +7,14 @@ const path = require('path');
 const NpmPackageJsonLint = require('./NpmPackageJsonLint');
 const Config = require('./Config');
 const glob = require('glob');
+const ignore = require('ignore');
 const ConfigValidator = require('./config/ConfigValidator');
 const Parser = require('./Parser');
 const pkg = require('../package.json');
 
+const DEFAULT_IGNORE_FILENAME = '.npmpackagejsonlintignore';
+const FILE_NOT_FOUND_ERROR_CODE = 'ENOENT';
+
 const noIssues = 0;
 
 /**
@@ -154,6 +158,31 @@ const isIssueAnError = function(issue) {
   return issue.severity === 'error';
 };
 
+/**
+ * Generates ignorer based on ignore file content.
+ *
+ * @param {String}            cwd     Current work directory.
+ * @param {CLIEngineOptions}  options CLIEngineOptions object.
+ * @returns {Object}          Ignorer
+ */
+const getIgnorer = function(cwd, options) {
+  const ignoreFilePath = options.ignorePath || DEFAULT_IGNORE_FILENAME;
+  const absoluteIgnoreFilePath = path.isAbsolute(ignoreFilePath)
+    ? ignoreFilePath
+    : path.resolve(cwd, ignoreFilePath);
+  let ignoreText = '';
+
+  try {
+    ignoreText = fs.readFileSync(absoluteIgnoreFilePath, 'utf8');
+  } catch (readError) {
+    if (readError.code !== FILE_NOT_FOUND_ERROR_CODE) {
+      throw readError;
+    }
+  }
+
+  return ignore().add(ignoreText);
+};
+
 /**
  * Generates a list of files to lint based on a list of provided patterns
  *
@@ -197,12 +226,13 @@ const getFileList = function(patterns, options) {
 
   const files = [];
   const addedFiles = new Set();
+  const ignorer = getIgnorer(cwd, options);
 
   globPatterns.forEach((pattern) => {
     const file = path.resolve(cwd, pattern);
 
     if (fs.existsSync(file) && fs.statSync(file).isFile()) {
-      if (addedFiles.has(file)) {
+      if (addedFiles.has(file) || ignorer.ignores(path.relative(cwd, file))) {
         return;
       }
 
@@ -225,7 +255,7 @@ const getFileList = function(patterns, options) {
       globFiles.forEach((globFile) => {
         const filePath = path.resolve(cwd, globFile);
 
-        if (addedFiles.has(filePath)) {
+        if (addedFiles.has(filePath) || ignorer.ignores(path.relative(cwd, filePath))) {
           return;
         }
 
diff --git a/src/cli.js b/src/cli.js
index ee58a37..6db7b49 100755
--- a/src/cli.js
+++ b/src/cli.js
@@ -24,6 +24,7 @@ const cli = meow(`
         --quiet, -q Report errors only
         --noConfigFiles, -ncf Disables use of .npmpackagejsonlintrc.json files, npmpackagejsonlint.config.js files, and npmPackageJsonLintConfig object in package.json file.
         --configFile, -c File path of .npmpackagejsonlintrc.json
+        --ignorePath, -i Path to a file containing patterns that describe files to ignore. The path can be absolute or relative to process.cwd(). By default, npm-package-json-lint looks for .npmpackagejsonlintignore in process.cwd().
 
       Examples
         $ npmPkgJsonLint --version
@@ -34,6 +35,8 @@ const cli = meow(`
         $ npmPkgJsonLint --configFile ./config/npmpackagejsonlint.config.json .
         $ npmPkgJsonLint -q .
         $ npmPkgJsonLint --quiet ./packages
+        $ npmPkgJsonLint . --ignorePath .gitignore
+        $ npmPkgJsonLint . -i .gitignore
 `, {
   flags: {
     quiet: {
@@ -50,6 +53,11 @@ const cli = meow(`
       'type': 'string',
       'alias': 'c',
       'default': ''
+    },
+    ignorePath: {
+      'type': 'string',
+      'alias': 'i',
+      'default': ''
     }
   }
 });
@@ -73,6 +81,7 @@ const cliEngineOptions = {
   configFile: cli.flags.configFile,
   cwd: process.cwd(),
   useConfigFiles: !cli.flags.noConfigFiles,
+  ignorePath: cli.flags.ignorePath,
   rules: {}
 };
 


Test patch:
diff --git a/tests/fixtures/ignorePath/.gitignore-example b/tests/fixtures/ignorePath/.gitignore-example
new file mode 100644
index 0000000..4277f2f
--- /dev/null
+++ b/tests/fixtures/ignorePath/.gitignore-example
@@ -0,0 +1,1 @@
+ignoredDirectory
diff --git a/tests/fixtures/ignorePath/.npmpackagejsonlintrc.json b/tests/fixtures/ignorePath/.npmpackagejsonlintrc.json
new file mode 100644
index 0000000..0d8251f
--- /dev/null
+++ b/tests/fixtures/ignorePath/.npmpackagejsonlintrc.json
@@ -0,0 +1,42 @@
+{
+  "rules": {
+    "require-author": "error",
+    "require-description": "error",
+    "require-devDependencies": "error",
+    "require-homepage": "error",
+    "require-keywords": "error",
+    "require-name": "error",
+    "require-repository": "error",
+    "require-version": "error",
+    "description-type": "error",
+    "devDependencies-type": "error",
+    "homepage-type": "error",
+    "keywords-type": "error",
+    "name-type": "error",
+    "repository-type": "error",
+    "version-type": "error",
+    "valid-values-author": ["error", [
+      "Thomas Lindner"
+    ]],
+    "name-format": "error",
+    "version-format": "error",
+    "prefer-property-order": ["error", [
+      "name",
+      "version",
+      "description",
+      "keywords",
+      "homepage",
+      "bugs",
+      "author",
+      "repository",
+      "bin",
+      "files",
+      "main",
+      "scripts",
+      "dependencies",
+      "devDependencies",
+      "engines",
+      "license"
+    ]]
+  }
+}
diff --git a/tests/fixtures/ignorePath/ignoredDirectory/package.json b/tests/fixtures/ignorePath/ignoredDirectory/package.json
new file mode 100644
index 0000000..4bc5f4e
--- /dev/null
+++ b/tests/fixtures/ignorePath/ignoredDirectory/package.json
@@ -0,0 +1,16 @@
+{
+  "name": "npm-package-json-lint",
+  "version": "0.1.0",
+  "description": "CLI app for linting package.json files.",
+  "keywords": [
+    "lint"
+  ],
+  "homepage": "https://github.com/tclindner/npm-package-json-lint",
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/tclindner/npm-package-json-lint"
+  },
+  "devDependencies": {
+    "mocha": "^2.4.5"
+  }
+}
diff --git a/tests/fixtures/ignorePath/package.json b/tests/fixtures/ignorePath/package.json
new file mode 100644
index 0000000..ad17e2a
--- /dev/null
+++ b/tests/fixtures/ignorePath/package.json
@@ -0,0 +1,17 @@
+{
+  "name": "npm-package-json-lint",
+  "version": "0.1.0",
+  "description": "CLI app for linting package.json files.",
+  "keywords": [
+    "lint"
+  ],
+  "homepage": "https://github.com/tclindner/npm-package-json-lint",
+  "author": "Thomas Lindner",
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/tclindner/npm-package-json-lint"
+  },
+  "devDependencies": {
+    "mocha": "^2.4.5"
+  }
+}
diff --git a/tests/fixtures/npmPackageJsonLintIgnore/.npmpackagejsonlintignore b/tests/fixtures/npmPackageJsonLintIgnore/.npmpackagejsonlintignore
new file mode 100644
index 0000000..4277f2f
--- /dev/null
+++ b/tests/fixtures/npmPackageJsonLintIgnore/.npmpackagejsonlintignore
@@ -0,0 +1,1 @@
+ignoredDirectory
diff --git a/tests/fixtures/npmPackageJsonLintIgnore/.npmpackagejsonlintrc.json b/tests/fixtures/npmPackageJsonLintIgnore/.npmpackagejsonlintrc.json
new file mode 100644
index 0000000..0d8251f
--- /dev/null
+++ b/tests/fixtures/npmPackageJsonLintIgnore/.npmpackagejsonlintrc.json
@@ -0,0 +1,42 @@
+{
+  "rules": {
+    "require-author": "error",
+    "require-description": "error",
+    "require-devDependencies": "error",
+    "require-homepage": "error",
+    "require-keywords": "error",
+    "require-name": "error",
+    "require-repository": "error",
+    "require-version": "error",
+    "description-type": "error",
+    "devDependencies-type": "error",
+    "homepage-type": "error",
+    "keywords-type": "error",
+    "name-type": "error",
+    "repository-type": "error",
+    "version-type": "error",
+    "valid-values-author": ["error", [
+      "Thomas Lindner"
+    ]],
+    "name-format": "error",
+    "version-format": "error",
+    "prefer-property-order": ["error", [
+      "name",
+      "version",
+      "description",
+      "keywords",
+      "homepage",
+      "bugs",
+      "author",
+      "repository",
+      "bin",
+      "files",
+      "main",
+      "scripts",
+      "dependencies",
+      "devDependencies",
+      "engines",
+      "license"
+    ]]
+  }
+}
diff --git a/tests/fixtures/npmPackageJsonLintIgnore/ignoredDirectory/package.json b/tests/fixtures/npmPackageJsonLintIgnore/ignoredDirectory/package.json
new file mode 100644
index 0000000..4bc5f4e
--- /dev/null
+++ b/tests/fixtures/npmPackageJsonLintIgnore/ignoredDirectory/package.json
@@ -0,0 +1,16 @@
+{
+  "name": "npm-package-json-lint",
+  "version": "0.1.0",
+  "description": "CLI app for linting package.json files.",
+  "keywords": [
+    "lint"
+  ],
+  "homepage": "https://github.com/tclindner/npm-package-json-lint",
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/tclindner/npm-package-json-lint"
+  },
+  "devDependencies": {
+    "mocha": "^2.4.5"
+  }
+}
diff --git a/tests/fixtures/npmPackageJsonLintIgnore/package.json b/tests/fixtures/npmPackageJsonLintIgnore/package.json
new file mode 100644
index 0000000..ad17e2a
--- /dev/null
+++ b/tests/fixtures/npmPackageJsonLintIgnore/package.json
@@ -0,0 +1,17 @@
+{
+  "name": "npm-package-json-lint",
+  "version": "0.1.0",
+  "description": "CLI app for linting package.json files.",
+  "keywords": [
+    "lint"
+  ],
+  "homepage": "https://github.com/tclindner/npm-package-json-lint",
+  "author": "Thomas Lindner",
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/tclindner/npm-package-json-lint"
+  },
+  "devDependencies": {
+    "mocha": "^2.4.5"
+  }
+}
diff --git a/tests/unit/CLIEngine.test.js b/tests/unit/CLIEngine.test.js
index 3ee21da..953ce8d 100644
--- a/tests/unit/CLIEngine.test.js
+++ b/tests/unit/CLIEngine.test.js
@@ -156,6 +156,88 @@ describe('CLIEngine Unit Tests', function() {
       results.should.deep.equal(expected);
     });
 
+    it('when called with patterns and ignorePath', function() {
+      const patterns = ['./tests/fixtures/ignorePath/'];
+      const ignorePath = path.resolve(__dirname, '../fixtures/ignorePath/.gitignore-example');
+
+      const expected = {
+        errorCount: 0,
+        results: [
+          {
+            errorCount: 0,
+            filePath: './tests/fixtures/ignorePath/package.json',
+            issues: [],
+            warningCount: 0
+          }
+        ],
+        warningCount: 0
+      };
+
+      const options = {
+        configFile: '',
+        cwd: process.cwd(),
+        useConfigFiles: true,
+        ignorePath,
+        rules: {}
+      };
+      const cliEngine = new CLIEngine(options);
+      const results = cliEngine.executeOnPackageJsonFiles(patterns);
+
+      results.should.deep.equal(expected);
+    });
+
+    it('when called with patterns should respect .npmpackagejsonlintignore', function() {
+      const cwd = path.resolve(__dirname, '../fixtures/npmPackageJsonLintIgnore');
+      const patterns = [cwd];
+
+      const expected = {
+        errorCount: 0,
+        results: [
+          {
+            errorCount: 0,
+            filePath: './package.json',
+            issues: [],
+            warningCount: 0
+          }
+        ],
+        warningCount: 0
+      };
+
+      const options = {
+        configFile: '',
+        cwd,
+        useConfigFiles: true,
+        rules: {}
+      };
+      const cliEngine = new CLIEngine(options);
+      const results = cliEngine.executeOnPackageJsonFiles(patterns);
+
+      results.should.deep.equal(expected);
+    });
+
+    it('when called with patterns (pattern is file) and ignorePath', function() {
+      const patterns = ['./tests/fixtures/ignorePath/ignoredDirectory/package.json'];
+      const ignorePath = path.resolve(__dirname, '../fixtures/ignorePath/.gitignore-example');
+
+      const expected = {
+        errorCount: 0,
+        results: [],
+        warningCount: 0
+      };
+
+      const options = {
+        configFile: '',
+        cwd: process.cwd(),
+        useConfigFiles: true,
+        ignorePath,
+        rules: {}
+      };
+      const cliEngine = new CLIEngine(options);
+      const results = cliEngine.executeOnPackageJsonFiles(patterns);
+
+      results.should.deep.equal(expected);
+    });
+
     it('when called with invalid pattern', function() {
       const pattern = './tests/fixtures/valid/.npmpackagejsonlintrc.json';
       const patterns = [pattern];
diff --git a/tests/unit/cli.test.js b/tests/unit/cli.test.js
index b2d0d9b..2e86ced 100755
--- a/tests/unit/cli.test.js
+++ b/tests/unit/cli.test.js
@@ -48,6 +48,7 @@ describe('cli Unit Tests', function() {
     --quiet, -q Report errors only
     --noConfigFiles, -ncf Disables use of .npmpackagejsonlintrc.json files, npmpackagejsonlint.config.js files, and npmPackageJsonLintConfig object in package.json file.
     --configFile, -c File path of .npmpackagejsonlintrc.json
+    --ignorePath, -i Path to a file containing patterns that describe files to ignore. The path can be absolute or relative to process.cwd(). By default, npm-package-json-lint looks for .npmpackagejsonlintignore in process.cwd().
 
   Examples
     $ npmPkgJsonLint --version
@@ -57,7 +58,9 @@ describe('cli Unit Tests', function() {
     $ npmPkgJsonLint -c ./config/.npmpackagejsonlintrc.json .
     $ npmPkgJsonLint --configFile ./config/npmpackagejsonlint.config.json .
     $ npmPkgJsonLint -q .
-    $ npmPkgJsonLint --quiet ./packages\n\n`;
+    $ npmPkgJsonLint --quiet ./packages
+    $ npmPkgJsonLint . --ignorePath .gitignore
+    $ npmPkgJsonLint . -i .gitignore\n\n`;
 
     it('with --help, a list of commands is printed', function() {
       const cli = spawnSync(relativePathToCli, ['--help']);