Repository: okonet/lint-staged (js)

Issue:
File matching a parent directory
I just have a question of file matching and dont even know if this is possible. I have my package.json in a subfolder `/docroot/themes/custom/my_theme/package.json` and I am using lint-staged to run some precommit git hooks, but I cant get the file pattern matching to work for files that are in parent directories.

I want to search for files in `/docroot/modules/custom/**/*.php`

I have tried

```
"docroot/modules/custom/**/*.php": [
  "phpcs --standard=Drupal"
]

"/docroot/modules/custom/**/*.php": [
  "phpcs --standard=Drupal"
]

"modules/custom/**/*.php": [
  "phpcs --standard=Drupal"
]

"../../../modules/custom/**/*.php": [
  "phpcs --standard=Drupal"
]
```

and I cannot get any of them to match files. My git root is in the `/docroot` folder. Any ideas?



Pull request description:
feat: Allow linting files outside of project folder
Files are not filtered only when pattern starts with `../`. This avoids adding another flag to the config and should work as expected for existing users.

Closes #425.
Closes #487.
Closes #492. 

Patch:
diff --git a/README.md b/README.md
index 3fa6785..557e0cd 100644
--- a/README.md
+++ b/README.md
@@ -133,15 +133,10 @@ To extend and customise lint-staged, advanced options are available. To use thes
 {
   "lint-staged": {
     "linters": {
-      "*.{js,scss}": [
-        "some command",
-        "git add"
-      ]
+      "*.{js,scss}": ["some command", "git add"]
     },
-    "ignore": [
-      "**/dist/*.min.js"
-    ]
-  },
+    "ignore": ["**/dist/*.min.js"]
+  }
 }
 ```
 
@@ -346,3 +341,16 @@ If you wish to use `lint-staged` in a multi package monorepo, it is recommended
 [`lerna`](https://github.com/lerna/lerna) can be used to execute the `precommit` script in all sub-packages.
 
 Example repo: [sudo-suhas/lint-staged-multi-pkg](https://github.com/sudo-suhas/lint-staged-multi-pkg).
+
+### Can I lint files outside of the current project folder?
+
+tl;dr: Yes, but the pattern should start with `../`.
+
+By default, `lint-staged` executes linters only on the files present inside the project folder(where `lint-staged` is installed and run from).
+So this question is relevant _only_ when the project folder is a child folder inside the git repo.
+In certain project setups, it might be desirable to bypass this restriction. See [#425](https://github.com/okonet/lint-staged/issues/425), [#487](https://github.com/okonet/lint-staged/issues/487) for more context.
+
+`lint-staged` provides an escape hatch for the same(`>= v7.3.0`). For patterns that start with `../`, all the staged files are allowed to match against the pattern.
+Note that patterns like `*.js`, `**/*.js` will still only match the project files and not any of the files in parent or sibling directories.
+
+Example repo: [sudo-suhas/lint-staged-django-react-demo](https://github.com/sudo-suhas/lint-staged-django-react-demo).
diff --git a/src/generateTasks.js b/src/generateTasks.js
index d4bdd80..14bd319 100644
--- a/src/generateTasks.js
+++ b/src/generateTasks.js
@@ -8,7 +8,7 @@ const resolveGitDir = require('./resolveGitDir')
 
 const debug = require('debug')('lint-staged:gen-tasks')
 
-module.exports = function generateTasks(config, relFiles) {
+module.exports = function generateTasks(config, stagedRelFiles) {
   debug('Generating linter tasks')
 
   const normalizedConfig = getConfig(config) // Ensure we have a normalized config
@@ -17,16 +17,18 @@ module.exports = function generateTasks(config, relFiles) {
 
   const gitDir = resolveGitDir()
   const cwd = process.cwd()
-  const files = relFiles.map(file => path.resolve(gitDir, file))
+  const stagedFiles = stagedRelFiles.map(file => path.resolve(gitDir, file))
 
   return Object.keys(linters).map(pattern => {
+    const isParentDirPattern = pattern.startsWith('../')
     const patterns = [pattern].concat(ignorePatterns)
     const commands = linters[pattern]
 
     const fileList = micromatch(
-      files
-        // Only worry about children of the CWD
-        .filter(file => pathIsInside(file, cwd))
+      stagedFiles
+        // Only worry about children of the CWD unless the pattern explicitly
+        // specifies that it concerns a parent directory.
+        .filter(file => isParentDirPattern || pathIsInside(file, cwd))
         // Make the paths relative to CWD for filtering
         .map(file => path.relative(cwd, file)),
       patterns,


Test patch:
diff --git a/test/generateTasks.spec.js b/test/generateTasks.spec.js
index f3e146b..8be57b5 100644
--- a/test/generateTasks.spec.js
+++ b/test/generateTasks.spec.js
@@ -87,7 +87,7 @@ describe('generateTasks', () => {
   })
 
   it('should not match non-children files', () => {
-    const relPath = path.resolve(path.join(process.cwd(), '..'))
+    const relPath = path.join(process.cwd(), '..')
     resolveGitDir.mockReturnValueOnce(relPath)
     const result = generateTasks(Object.assign({}, config), files)
     const linter = result.find(item => item.pattern === '*.js')
@@ -222,4 +222,31 @@ describe('generateTasks', () => {
       fileList: [`${workDir}/cool/js.js`].map(path.normalize)
     })
   })
+
+  it('should not filter files for pattern which begins with `..`', () => {
+    jest.spyOn(process, 'cwd').mockReturnValueOnce(path.join(workDir, 'prj'))
+    const result = generateTasks(
+      {
+        linters: {
+          '*.js': 'my-cmd',
+          '../outside/*.js': 'my-cmd'
+        }
+      },
+      ['root.js', 'prj/test.js', 'outside/test.js', 'outside/test2.js']
+    )
+
+    const prjTask = result.find(item => item.pattern === '*.js')
+    expect(prjTask).toEqual({
+      pattern: '*.js',
+      commands: 'my-cmd',
+      fileList: [`${workDir}/prj/test.js`].map(path.normalize)
+    })
+
+    const parentFolderTask = result.find(item => item.pattern === '../outside/*.js')
+    expect(parentFolderTask).toEqual({
+      pattern: '../outside/*.js',
+      commands: 'my-cmd',
+      fileList: [`${workDir}/outside/test.js`, `${workDir}/outside/test2.js`].map(path.normalize)
+    })
+  })
 })