Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add ability to use function as config #913

Merged
merged 5 commits into from Sep 16, 2020
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
26 changes: 23 additions & 3 deletions README.md
Expand Up @@ -189,14 +189,33 @@ For example:

going to execute `eslint` and if it exits with `0` code, it will execute `prettier --write` on all staged `*.js` files.

## Using JS functions to customize tasks
## Using JS configuration file

When supplying configuration in JS format it is possible to define the task as a function, which will receive an array of staged filenames/paths and should return the complete command as a string. It is also possible to return an array of complete command strings, for example when the task supports only a single file input. The function can be either sync or async.
Writing `lint-staged.config.js` file (or, other JS file which needs to be mentioned in CLI command through `c` flag) is the most powerful way to configure `lint-staged`. From the configuration file, you can export a function which gets an array of all staged file paths as parameter and returns command(s) as string or array of string. This function can be either sync or async. Signature of this function:
SachinShekhar marked this conversation as resolved.
Show resolved Hide resolved

```ts
type TaskFn = (filenames: string[]) => string | string[] | Promise<string | string[]>
(filenames: string[]) => string | string[] | Promise<string | string[]>
SachinShekhar marked this conversation as resolved.
Show resolved Hide resolved
```

### Example:
SachinShekhar marked this conversation as resolved.
Show resolved Hide resolved

```js
// lint-staged.config.js
const micromatch = require('micromatch')

module.exports = (allStagedFiles) => {
const shFiles = micromatch(allStagedFiles, ['**/src/**/*.sh']);
if (shFiles.length) {
return `printf '%s\n' "Script files aren't allowed in src directory" >&2`
}
const codeFiles = micromatch(allStagedFiles, ['**/*.js', '**/*.ts']);
const docFiles = micromatch(allStagedFiles, ['**/*.md']);
return [`eslint ${codeFiles.join(' ')}`, `mdl ${docFiles.join(' ')}`];
}
```

You can also supply configuration as an object to enjoy in-built filter mechanism like other configuration methods. One extra advantage in the case of JS config is that it is possible to define the task as a function, which has signature of the above function (The only difference is that task functions don't receive array of all staged file paths as parameter unless they have `*` filter pattern).
iiroj marked this conversation as resolved.
Show resolved Hide resolved
SachinShekhar marked this conversation as resolved.
Show resolved Hide resolved

### Example: Wrap filenames in single quotes and run once per file

```js
Expand Down Expand Up @@ -226,6 +245,7 @@ module.exports = {
```

### Example: Use your own globs
It's better to use function-based configuration if your use case is this.
iiroj marked this conversation as resolved.
Show resolved Hide resolved
SachinShekhar marked this conversation as resolved.
Show resolved Hide resolved

```js
// lint-staged.config.js
Expand Down
7 changes: 7 additions & 0 deletions lib/formatConfig.js
@@ -0,0 +1,7 @@
module.exports = function formatConfig(config) {
if (typeof config === 'function') {
return { '*': config }
}

return config
}
4 changes: 3 additions & 1 deletion lib/index.js
Expand Up @@ -9,6 +9,7 @@ const { PREVENTED_EMPTY_COMMIT, GIT_ERROR, RESTORE_STASH_EXAMPLE } = require('./
const printTaskOutput = require('./printTaskOutput')
const runAll = require('./runAll')
const { ApplyEmptyCommitError, GetBackupStashError, GitError } = require('./symbols')
const formatConfig = require('./formatConfig')
const validateConfig = require('./validateConfig')

const errConfigNotFound = new Error('Config could not be found')
Expand Down Expand Up @@ -88,7 +89,8 @@ module.exports = async function lintStaged(
debugLog('Successfully loaded config from `%s`:\n%O', resolved.filepath, resolved.config)
// resolved.config is the parsed configuration object
// resolved.filepath is the path to the config file that was found
const config = validateConfig(resolved.config)
const formattedConfig = formatConfig(resolved.config)
const config = validateConfig(formattedConfig)
if (debug) {
// Log using logger to be able to test through `consolemock`.
logger.log('Running lint-staged with the following config:')
Expand Down
2 changes: 2 additions & 0 deletions test/__snapshots__/validateConfig.spec.js.snap
@@ -1,5 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`validateConfig should not throw and should print nothing for function config 1`] = `""`;

exports[`validateConfig should not throw and should print nothing for function task 1`] = `""`;

exports[`validateConfig should not throw and should print nothing for valid config 1`] = `""`;
Expand Down
17 changes: 17 additions & 0 deletions test/formatConfig.spec.js
@@ -0,0 +1,17 @@
import formatConfig from '../lib/formatConfig'

describe('formatConfig', () => {
it('Object config should return as is', () => {
const simpleConfig = {
'*.js': ['eslint --fix', 'git add'],
}
expect(formatConfig(simpleConfig)).toEqual(simpleConfig)
})

it('Function config should be converted to object', () => {
const functionConfig = (stagedFiles) => [`eslint --fix ${stagedFiles}', 'git add`]
expect(formatConfig(functionConfig)).toEqual({
'*': functionConfig,
})
})
})
8 changes: 8 additions & 0 deletions test/validateConfig.spec.js
Expand Up @@ -2,6 +2,8 @@ import makeConsoleMock from 'consolemock'

import validateConfig from '../lib/validateConfig'

import formatConfig from '../lib/formatConfig'

describe('validateConfig', () => {
const originalConsole = global.console
beforeAll(() => {
Expand Down Expand Up @@ -36,6 +38,12 @@ describe('validateConfig', () => {
expect(console.printHistory()).toMatchSnapshot()
})

it('should not throw and should print nothing for function config', () => {
const functionConfig = (stagedFiles) => [`eslint ${stagedFiles.join(' ')}`]
expect(() => validateConfig(formatConfig(functionConfig))).not.toThrow()
expect(console.printHistory()).toMatchSnapshot()
})

it('should not throw and should print nothing for function task', () => {
expect(() =>
validateConfig({
Expand Down