Skip to content

Commit

Permalink
perf: Switch from minimatch to micromatch (#388)
Browse files Browse the repository at this point in the history
Since micromatch supports multiple patterns for filtering, filter staged files
in a single pass for match and ignore patterns.

BREAKING CHANGE: The following `minimatch` options are not supported in
`micromatch`:
- `nocomment`: https://github.com/isaacs/minimatch#nocomment
- `flipnegate`: https://github.com/isaacs/minimatch#flipnegate
  • Loading branch information
sudo-suhas committed Feb 21, 2018
1 parent 6ace14e commit 5a333a0
Show file tree
Hide file tree
Showing 4 changed files with 501 additions and 27 deletions.
6 changes: 3 additions & 3 deletions README.md
Expand Up @@ -91,7 +91,7 @@ Lint-staged supports simple and advanced config formats.

### Simple config format

Should be an object where each value is a command to run and its key is a glob pattern to use for this command. This package uses [minimatch](https://github.com/isaacs/minimatch) for glob patterns.
Should be an object where each value is a command to run and its key is a glob pattern to use for this command. This package uses [micromatch](https://github.com/micromatch/micromatch) for glob patterns.

#### `package.json` example:
```json
Expand Down Expand Up @@ -126,15 +126,15 @@ To set options and keep lint-staged extensible, advanced format can be used. Thi

* `concurrent`*true* — runs linters for each glob pattern simultaneously. If you don’t want this, you can set `concurrent: false`
* `chunkSize` — Max allowed chunk size based on number of files for glob pattern. This is important on windows based systems to avoid command length limitations. See [#147](https://github.com/okonet/lint-staged/issues/147)
* `globOptions``{ matchBase: true, dot: true }`[minimatch options](https://github.com/isaacs/minimatch#options) to
* `globOptions``{ matchBase: true, dot: true }`[micromatch options](https://github.com/micromatch/micromatch#options) to
customize how glob patterns match files.
* `ignore` - `['**/docs/**/*.js']` - array of glob patterns to entirely ignore from any task.
* `linters``Object` — keys (`String`) are glob patterns, values (`Array<String> | String`) are commands to execute.
* `subTaskConcurrency``1` — Controls concurrency for processing chunks generated for each linter. Execution is **not** concurrent by default(see [#225](https://github.com/okonet/lint-staged/issues/225))

## Filtering files

It is possible to run linters for certain paths only by using glob patterns. [minimatch](https://github.com/isaacs/minimatch) is used to filter the staged files according to these patterns. File patterns should be specified _relative to the `package.json` location_ (i.e. where `lint-staged` is installed).
It is possible to run linters for certain paths only by using glob patterns. [micromatch](https://github.com/micromatch/micromatch) is used to filter the staged files according to these patterns. File patterns should be specified _relative to the `package.json` location_ (i.e. where `lint-staged` is installed).

**NOTE:** If you're using `lint-staged<5` globs have to be _relative to the git root_.

Expand Down
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -40,7 +40,7 @@
"listr": "^0.13.0",
"lodash": "^4.17.4",
"log-symbols": "^2.0.0",
"minimatch": "^3.0.0",
"micromatch": "^3.1.5",
"npm-which": "^3.0.1",
"p-map": "^1.1.1",
"path-is-inside": "^1.0.2",
Expand Down
30 changes: 13 additions & 17 deletions src/generateTasks.js
@@ -1,7 +1,7 @@
'use strict'

const path = require('path')
const minimatch = require('minimatch')
const micromatch = require('micromatch')
const pathIsInside = require('path-is-inside')
const getConfig = require('./getConfig').getConfig
const resolveGitDir = require('./resolveGitDir')
Expand All @@ -14,29 +14,25 @@ module.exports = function generateTasks(config, relFiles) {
const normalizedConfig = getConfig(config) // Ensure we have a normalized config
const linters = normalizedConfig.linters
const globOptions = normalizedConfig.globOptions
const ignoreFilters = normalizedConfig.ignore.map(pattern => minimatch.filter(pattern))
// if there are filters, then return false if the input matches any
// if there are not, then return true for all input
const ignoreFilter = ignoreFilters.length
? input => !ignoreFilters.some(filter => filter(input))
: () => true
const ignorePatterns = normalizedConfig.ignore.map(pattern => `!${pattern}`)

const gitDir = resolveGitDir()
const cwd = process.cwd()
const files = relFiles.map(file => path.resolve(gitDir, file))

return Object.keys(linters).map(pattern => {
const patterns = [pattern].concat(ignorePatterns)
const commands = linters[pattern]
const filter = minimatch.filter(pattern, globOptions)

const fileList = files
// Only worry about children of the CWD
.filter(file => pathIsInside(file, cwd))
// Make the paths relative to CWD for filtering
.map(file => path.relative(cwd, file))
// We want to filter before resolving paths
.filter(filter)
.filter(ignoreFilter)

const fileList = micromatch(
files
// Only worry about children of the CWD
.filter(file => pathIsInside(file, cwd))
// Make the paths relative to CWD for filtering
.map(file => path.relative(cwd, file)),
patterns,
globOptions
)
// Return absolute path after the filter is run
.map(file => path.resolve(cwd, file))

Expand Down

0 comments on commit 5a333a0

Please sign in to comment.