Skip to content

Commit 67a4d06

Browse files
SachinShekhariiroj
andauthored
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 <[email protected]> Co-authored-by: Iiro Jäppinen <[email protected]>
1 parent 643038d commit 67a4d06

File tree

6 files changed

+67
-4
lines changed

6 files changed

+67
-4
lines changed

README.md

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -189,14 +189,40 @@ For example:
189189
190190
going to execute `eslint` and if it exits with `0` code, it will execute `prettier --write` on all staged `*.js` files.
191191
192-
## Using JS functions to customize tasks
192+
## Using JS configuration file
193193
194-
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.
194+
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.
195+
196+
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.
197+
198+
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.
199+
200+
### Function signature
201+
202+
The function can also be async:
195203
196204
```ts
197-
type TaskFn = (filenames: string[]) => string | string[] | Promise<string | string[]>
205+
(filenames: string[]) => string | string[] | Promise<string | string[]>
198206
```
199207
208+
### Example: Export a function to build your own matchers
209+
210+
```js
211+
// lint-staged.config.js
212+
const micromatch = require('micromatch')
213+
214+
module.exports = (allStagedFiles) => {
215+
const shFiles = micromatch(allStagedFiles, ['**/src/**/*.sh']);
216+
if (shFiles.length) {
217+
return `printf '%s\n' "Script files aren't allowed in src directory" >&2`
218+
}
219+
const codeFiles = micromatch(allStagedFiles, ['**/*.js', '**/*.ts']);
220+
const docFiles = micromatch(allStagedFiles, ['**/*.md']);
221+
return [`eslint ${codeFiles.join(' ')}`, `mdl ${docFiles.join(' ')}`];
222+
}
223+
```
224+
225+
200226
### Example: Wrap filenames in single quotes and run once per file
201227
202228
```js
@@ -226,6 +252,7 @@ module.exports = {
226252
```
227253
228254
### Example: Use your own globs
255+
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.
229256
230257
```js
231258
// lint-staged.config.js

lib/formatConfig.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
module.exports = function formatConfig(config) {
2+
if (typeof config === 'function') {
3+
return { '*': config }
4+
}
5+
6+
return config
7+
}

lib/index.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const { PREVENTED_EMPTY_COMMIT, GIT_ERROR, RESTORE_STASH_EXAMPLE } = require('./
99
const printTaskOutput = require('./printTaskOutput')
1010
const runAll = require('./runAll')
1111
const { ApplyEmptyCommitError, GetBackupStashError, GitError } = require('./symbols')
12+
const formatConfig = require('./formatConfig')
1213
const validateConfig = require('./validateConfig')
1314

1415
const errConfigNotFound = new Error('Config could not be found')
@@ -88,7 +89,8 @@ module.exports = async function lintStaged(
8889
debugLog('Successfully loaded config from `%s`:\n%O', resolved.filepath, resolved.config)
8990
// resolved.config is the parsed configuration object
9091
// resolved.filepath is the path to the config file that was found
91-
const config = validateConfig(resolved.config)
92+
const formattedConfig = formatConfig(resolved.config)
93+
const config = validateConfig(formattedConfig)
9294
if (debug) {
9395
// Log using logger to be able to test through `consolemock`.
9496
logger.log('Running lint-staged with the following config:')

test/__snapshots__/validateConfig.spec.js.snap

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3+
exports[`validateConfig should not throw and should print nothing for function config 1`] = `""`;
4+
35
exports[`validateConfig should not throw and should print nothing for function task 1`] = `""`;
46

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

test/formatConfig.spec.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import formatConfig from '../lib/formatConfig'
2+
3+
describe('formatConfig', () => {
4+
it('Object config should return as is', () => {
5+
const simpleConfig = {
6+
'*.js': ['eslint --fix', 'git add'],
7+
}
8+
expect(formatConfig(simpleConfig)).toEqual(simpleConfig)
9+
})
10+
11+
it('Function config should be converted to object', () => {
12+
const functionConfig = (stagedFiles) => [`eslint --fix ${stagedFiles}', 'git add`]
13+
expect(formatConfig(functionConfig)).toEqual({
14+
'*': functionConfig,
15+
})
16+
})
17+
})

test/validateConfig.spec.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import makeConsoleMock from 'consolemock'
22

33
import validateConfig from '../lib/validateConfig'
44

5+
import formatConfig from '../lib/formatConfig'
6+
57
describe('validateConfig', () => {
68
const originalConsole = global.console
79
beforeAll(() => {
@@ -36,6 +38,12 @@ describe('validateConfig', () => {
3638
expect(console.printHistory()).toMatchSnapshot()
3739
})
3840

41+
it('should not throw and should print nothing for function config', () => {
42+
const functionConfig = (stagedFiles) => [`eslint ${stagedFiles.join(' ')}`]
43+
expect(() => validateConfig(formatConfig(functionConfig))).not.toThrow()
44+
expect(console.printHistory()).toMatchSnapshot()
45+
})
46+
3947
it('should not throw and should print nothing for function task', () => {
4048
expect(() =>
4149
validateConfig({

0 commit comments

Comments
 (0)