Skip to content
Permalink
Browse files

feat: Add ability to use function as config (#913)

* feat: add ability to use function as config

* fix: test

* test: add specific test for config formatter

* docs: clarify function based configuration option

* Apply suggestions for README.md from code review

Co-authored-by: Iiro Jäppinen <iiro@jappinen.fi>

Co-authored-by: Iiro Jäppinen <iiro@jappinen.fi>
  • Loading branch information
SachinShekhar and iiroj committed Sep 16, 2020
1 parent 643038d commit 67a4d06e39c4638a546494940bf99934692fb610
Showing with 67 additions and 4 deletions.
  1. +30 −3 README.md
  2. +7 −0 lib/formatConfig.js
  3. +3 −1 lib/index.js
  4. +2 −0 test/__snapshots__/validateConfig.spec.js.snap
  5. +17 −0 test/formatConfig.spec.js
  6. +8 −0 test/validateConfig.spec.js
@@ -189,14 +189,40 @@ 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 the configuration file in JavaScript is the most powerful way to configure _lint-staged_ (`lint-staged.config.js`, [similar](https://github.com/okonet/lint-staged/README.md#configuration), or passed via `--config`). From the configuration file, you can export either a single function, or an object.
If the `exports` value is a function, it will receive an array of all staged filenames. You can then build your own matchers for the files, and return a command string, or an array or command strings. These strings are considered complete and should include the filename arguments, if wanted.
If the `exports` value is an object, its keys should be glob matches (like in the normal non-js config format). The values can either be like in the normal config, or individual functions like described above. Instead of receiving all matched files, the functions in the exported object will only receive the staged files matching the corresponding glob key.
### Function signature
The function can also be async:
```ts
type TaskFn = (filenames: string[]) => string | string[] | Promise<string | string[]>
(filenames: string[]) => string | string[] | Promise<string | string[]>
```
### Example: Export a function to build your own matchers
```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(' ')}`];
}
```
### Example: Wrap filenames in single quotes and run once per file
```js
@@ -226,6 +252,7 @@ module.exports = {
```
### Example: Use your own globs
It's better to use the [function-based configuration (seen above)](https://github.com/okonet/lint-staged/README.md#example-export-a-function-to-build-your-own-matchers), if your use case is this.
```js
// lint-staged.config.js
@@ -0,0 +1,7 @@
module.exports = function formatConfig(config) {
if (typeof config === 'function') {
return { '*': config }
}

return config
}
@@ -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')
@@ -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:')
@@ -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`] = `""`;
@@ -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,
})
})
})
@@ -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(() => {
@@ -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({

0 comments on commit 67a4d06

Please sign in to comment.
You can’t perform that action at this time.