Skip to content

Commit

Permalink
fix: Generate absolute paths for all paths relative to git root (#121)
Browse files Browse the repository at this point in the history
Fixes #118
Closes #120
  • Loading branch information
rodneyrehm authored and okonet committed Jan 18, 2017
1 parent 6fd017b commit 41ae0cb
Show file tree
Hide file tree
Showing 6 changed files with 143 additions and 114 deletions.
23 changes: 20 additions & 3 deletions README.md
Expand Up @@ -73,9 +73,26 @@ To set options and keep lint-staged extensible, advanced format can be used. Thi
## Options

* `linters``Object` — keys (`String`) are glob patterns, values (`Array<String> | String`) are commands to execute.
* `gitDir` — Sets the relative path to the `.git` root. Useful when your `package.json` is located in a sub-directory. See [working from a subdirectory](#working-from-a-subdirectory)
* `gitDir` — Sets the relative path to the `.git` root. Useful when your `package.json` is located in a sub-directory. See [working from a sub-directory](#working-from-a-sub-directory)
* `concurrent`*true* — runs linters for each glob pattern simultaneously. If you don’t want this, you can set `concurrent: false`

## Filtering files

It is possible to run linters for certain paths only by using [minimatch](https://github.com/isaacs/minimatch) patterns. The paths used for filtering via minimatch are relative to the directory that contains the `.git` directory. The paths passed to the linters are absolute to avoid confusion in case they're executed with a different working directory, as would be the case when using the `gitDir` option.

```js
{
// .js files anywhere in the project
"*.js": "eslint",
// .js files anywhere in the project
"**/*.js": "eslint",
// .js file in the src directory
"src/*.js": "eslint",
// .js file anywhere within and below the src directory
"src/**/*.js": "eslint",
}
```

## What commands are supported?

Supported are both local npm scripts (`npm run-script`), or any executables installed locally or globally via `npm` as well as any executable from your $PATH.
Expand Down Expand Up @@ -106,9 +123,9 @@ Tools like ESLint or stylefmt can re-format your code according to an appropriat

Starting from v3.1, lint-staged will stash you remaining changes (not added to the index) and restore them from stash afterwards. This allows you to create partial commits with hunks using `git add --patch`.

## Working from a subdirectory
## Working from a sub-directory

If your `package.json` is located in a subdirectory of the git root directory, you can use `gitDir` relative path to point there in order to make lint-staged work.
If your `package.json` is located in a sub-directory of the git root directory, you can use `gitDir` relative path to point there in order to make lint-staged work.

```json
{
Expand Down
14 changes: 7 additions & 7 deletions src/generateTasks.js
Expand Up @@ -2,17 +2,17 @@

const minimatch = require('minimatch')

module.exports = function generateTasks(config, filePaths) {
module.exports = function generateTasks(config, files) {
const linters = config.linters !== undefined ? config.linters : config
const resolve = file => files[file]
return Object.keys(linters)
.map((pattern) => {
const commands = linters[pattern]
const fileList = filePaths.filter(
minimatch.filter(pattern, {
matchBase: true,
dot: true
})
)
const filter = minimatch.filter(pattern, {
matchBase: true,
dot: true
})
const fileList = Object.keys(files).filter(filter).map(resolve)
if (fileList.length) {
return {
pattern,
Expand Down
17 changes: 9 additions & 8 deletions src/index.js
Expand Up @@ -11,7 +11,6 @@ const cosmiconfig = require('cosmiconfig')

const packageJson = require(appRoot.resolve('package.json')) // eslint-disable-line
const runScript = require('./runScript')
const resolvePaths = require('./resolvePaths')
const generateTasks = require('./generateTasks')

// Force colors for packages that depend on https://www.npmjs.com/package/supports-color
Expand All @@ -30,18 +29,21 @@ cosmiconfig('lint-staged', {
const config = result.config
const concurrent = config.concurrent || true
const gitDir = config.gitDir ? path.resolve(config.gitDir) : process.cwd()

// If gitDir is defined -> set git root as sgf's cwd
if (gitDir !== process.cwd()) {
sgf.cwd = gitDir
}
sgf.cwd = gitDir

sgf('ACM', (err, files) => {
if (err) {
console.error(err)
}

const tasks = generateTasks(config, resolvePaths(files, gitDir))
const resolvedFiles = {}
files.forEach((file) => {
const absolute = path.resolve(gitDir, file.filename)
const relative = path.relative(gitDir, absolute)
resolvedFiles[relative] = absolute
})

const tasks = generateTasks(config, resolvedFiles)
.map(task => ({
title: `Running tasks for ${ task.pattern }`,
task: () => (
Expand All @@ -68,4 +70,3 @@ ${ parsingError }
`)
process.exit(1)
})

9 changes: 0 additions & 9 deletions src/resolvePaths.js

This file was deleted.

158 changes: 107 additions & 51 deletions test/generateTasks.spec.js
@@ -1,68 +1,124 @@
import expect from 'expect'
import generateTasks from '../src/generateTasks'

const simpleConfig = {
'*.css': 'stylelint'
}
const nestedConfig = {
gitDir: '../',
linters: {
'*.js': ['eslint --fix', 'git add'],
'*.css': 'stylelint'
}
const files = {
'test.js': '/root/test.js',
'deeper/test.js': '/root/deeper/test.js',
'deeper/test2.js': '/root/deeper/test2.js',
'even/deeper/test.js': '/root/even/deeper/test.js',
'.hidden/test.js': '/root/.hidden/test.js',

'test.css': '/root/test.css',
'deeper/test.css': '/root/deeper/test.css',
'deeper/test2.css': '/root/deeper/test2.css',
'even/deeper/test.css': '/root/even/deeper/test.css',
'.hidden/test.css': '/root/.hidden/test.css',

'test.txt': '/root/test.txt',
'deeper/test.txt': '/root/deeper/test.txt',
'deeper/test2.txt': '/root/deeper/test2.txt',
'even/deeper/test.txt': '/root/even/deeper/test.txt',
'.hidden/test.txt': '/root/.hidden/test.txt'
}

const files = ['test.js', 'src/.hidden/test2.js', '/.storybook/test.css', '.storybook/test.css', '/absolute/path/test.txt']
const linters = {
'*.js': 'root-js',
'**/*.js': 'any-js',
'deeper/*.js': 'deeper-js',
'.hidden/*.js': 'hidden-js',
'unknown/*.js': 'unknown-js',
'*.{css,js}': 'root-css-or-js'
}

describe('generateTasks', () => {
it('should not generate tasks for non-matching files', () => {
const res = generateTasks(simpleConfig, ['test'])
expect(res).toBeAn('array')
expect(res.length).toEqual(0)
it('should return only linters it could find files for', () => {
const result = generateTasks(linters, files)
const commands = result.map(match => match.commands)
expect(commands).toEqual([
'root-js',
'any-js',
'deeper-js',
'hidden-js',
// 'unknown-js' does not match any files
'root-css-or-js'
])
})

it('should generate tasks for files with dots in path', () => {
const res = generateTasks(simpleConfig, files)
expect(res).toBeAn('array')
expect(res[0].fileList).toEqual(['/.storybook/test.css', '.storybook/test.css'])
it('should match pattern "*.js"', () => {
const result = generateTasks(linters, files)
const linter = result.find(item => item.pattern === '*.js')
expect(linter).toEqual({
pattern: '*.js',
commands: 'root-js',
fileList: [
'/root/test.js',
'/root/deeper/test.js',
'/root/deeper/test2.js',
'/root/even/deeper/test.js',
'/root/.hidden/test.js'
]
})
})

it('should generate tasks with complex globs', () => {
const res = generateTasks({
'**/*.css': 'stylelint'
}, files)
expect(res).toBeAn('array')
expect(res[0].fileList).toEqual(['/.storybook/test.css', '.storybook/test.css'])
it('should match pattern "**/*.js"', () => {
const result = generateTasks(linters, files)
const linter = result.find(item => item.pattern === '**/*.js')
expect(linter).toEqual({
pattern: '**/*.js',
commands: 'any-js',
fileList: [
'/root/test.js',
'/root/deeper/test.js',
'/root/deeper/test2.js',
'/root/even/deeper/test.js',
'/root/.hidden/test.js'
]
})
})

it('should generate tasks for simple config', () => {
const res = generateTasks(simpleConfig, files)
expect(res).toBeAn('array')
expect(res.length).toBe(1)
expect(res).toEqual([
{
pattern: '*.css',
commands: 'stylelint',
fileList: ['/.storybook/test.css', '.storybook/test.css']
}
])
it('should match pattern "deeper/*.js"', () => {
const result = generateTasks(linters, files)
const linter = result.find(item => item.pattern === 'deeper/*.js')
expect(linter).toEqual({
pattern: 'deeper/*.js',
commands: 'deeper-js',
fileList: [
'/root/deeper/test.js',
'/root/deeper/test2.js'
]
})
})

it('should generate tasks for nested config', () => {
const res = generateTasks(nestedConfig, files)
expect(res).toBeAn('array')
expect(res.length).toBe(2)
expect(res).toEqual([
{
pattern: '*.js',
commands: ['eslint --fix', 'git add'],
fileList: ['test.js', 'src/.hidden/test2.js']
},
{
pattern: '*.css',
commands: 'stylelint',
fileList: ['/.storybook/test.css', '.storybook/test.css']
}
])
it('should match pattern ".hidden/*.js"', () => {
const result = generateTasks(linters, files)
const linter = result.find(item => item.pattern === '.hidden/*.js')
expect(linter).toEqual({
pattern: '.hidden/*.js',
commands: 'hidden-js',
fileList: [
'/root/.hidden/test.js'
]
})
})

it('should match pattern "*.{css,js}"', () => {
const result = generateTasks(linters, files)
const linter = result.find(item => item.pattern === '*.{css,js}')
expect(linter).toEqual({
pattern: '*.{css,js}',
commands: 'root-css-or-js',
fileList: [
'/root/test.js',
'/root/deeper/test.js',
'/root/deeper/test2.js',
'/root/even/deeper/test.js',
'/root/.hidden/test.js',
'/root/test.css',
'/root/deeper/test.css',
'/root/deeper/test2.css',
'/root/even/deeper/test.css',
'/root/.hidden/test.css'
]
})
})
})
36 changes: 0 additions & 36 deletions test/resolvePaths.spec.js

This file was deleted.

0 comments on commit 41ae0cb

Please sign in to comment.