Skip to content

Commit 20d5c5d

Browse files
committed
feat: support async function tasks
1 parent f2a2702 commit 20d5c5d

File tree

4 files changed

+39
-29
lines changed

4 files changed

+39
-29
lines changed

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -153,12 +153,12 @@ Pass arguments to your commands separated by space as you would do in the shell.
153153

154154
Starting from [v2.0.0](https://github.com/okonet/lint-staged/releases/tag/2.0.0) sequences of commands are supported. Pass an array of commands instead of a single one and they will run sequentially. This is useful for running autoformatting tools like `eslint --fix` or `stylefmt` but can be used for any arbitrary sequences.
155155

156-
## Using JS functions to customize linter commands
156+
## Using JS functions to customize tasks
157157

158-
When supplying configuration in JS format it is possible to define the linter command as a function which receives an array of staged filenames/paths and returns the complete linter command as a string. It is also possible to return an array of complete command strings, for example when the linter command supports only a single file input.
158+
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.
159159

160160
```ts
161-
type LinterFn = (filenames: string[]) => string | string[]
161+
type TaskFn = (filenames: string[]) => string | string[] | Promise<string | string[]>
162162
```
163163
164164
### Example: Wrap filenames in single quotes and run once per file
@@ -196,7 +196,7 @@ const micromatch = require('micromatch')
196196
module.exports = {
197197
'*': allFiles => {
198198
const match = micromatch(allFiles, ['*.js', '*.ts'])
199-
return `eslint ${match.join(" ")}`
199+
return `eslint ${match.join(' ')}`
200200
}
201201
}
202202
```
@@ -212,7 +212,7 @@ module.exports = {
212212
'*.js': files => {
213213
// from `files` filter those _NOT_ matching `*test.js`
214214
const match = micromatch.not(files, '*test.js')
215-
return `eslint ${match.join(" ")}`
215+
return `eslint ${match.join(' ')}`
216216
}
217217
}
218218
```

lib/makeCmdTasks.js

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,41 +13,43 @@ const debug = require('debug')('lint-staged:make-cmd-tasks')
1313
* @param {string} options.gitDir
1414
* @param {Boolean} shell
1515
*/
16-
module.exports = function makeCmdTasks({ commands, files, gitDir, shell }) {
16+
module.exports = async function makeCmdTasks({ commands, files, gitDir, shell }) {
1717
debug('Creating listr tasks for commands %o', commands)
1818
const commandsArray = Array.isArray(commands) ? commands : [commands]
19+
const cmdTasks = []
1920

20-
return commandsArray.reduce((tasks, command) => {
21+
for (const cmd of commandsArray) {
2122
// command function may return array of commands that already include `stagedFiles`
22-
const isFn = typeof command === 'function'
23-
const resolved = isFn ? command(files) : command
24-
const commands = Array.isArray(resolved) ? resolved : [resolved] // Wrap non-array command as array
23+
const isFn = typeof cmd === 'function'
24+
const resolved = isFn ? await cmd(files) : cmd
25+
26+
const resolvedArray = Array.isArray(resolved) ? resolved : [resolved] // Wrap non-array command as array
2527

2628
// Function command should not be used as the task title as-is
2729
// because the resolved string it might be very long
2830
// Create a matching command array with [file] in place of file names
29-
let mockCommands
31+
let mockCmdTasks
3032
if (isFn) {
3133
const mockFileList = Array(files.length).fill('[file]')
32-
const resolved = command(mockFileList)
33-
mockCommands = Array.isArray(resolved) ? resolved : [resolved]
34+
const resolved = await cmd(mockFileList)
35+
mockCmdTasks = Array.isArray(resolved) ? resolved : [resolved]
3436
}
3537

36-
commands.forEach((command, i) => {
38+
for (const [i, command] of resolvedArray.entries()) {
3739
let title = isFn ? '[Function]' : command
38-
if (isFn && mockCommands[i]) {
40+
if (isFn && mockCmdTasks[i]) {
3941
// If command is a function, use the matching mock command as title,
4042
// but since might include multiple [file] arguments, shorten to one
41-
title = mockCommands[i].replace(/\[file\].*\[file\]/, '[file]')
43+
title = mockCmdTasks[i].replace(/\[file\].*\[file\]/, '[file]')
4244
}
4345

44-
tasks.push({
46+
cmdTasks.push({
4547
title,
4648
command,
4749
task: resolveTaskFn({ command, files, gitDir, isFn, shell })
4850
})
49-
})
51+
}
52+
}
5053

51-
return tasks
52-
}, [])
54+
return cmdTasks
5355
}

lib/runAll.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,10 @@ module.exports = async function runAll(
8181

8282
for (const [index, files] of stagedFileChunks.entries()) {
8383
const chunkTasks = generateTasks({ config, cwd, gitDir, files, relative })
84-
const chunkListrTasks = chunkTasks.map(task => {
85-
const subTasks = makeCmdTasks({
84+
const chunkListrTasks = []
85+
86+
for (const task of chunkTasks) {
87+
const subTasks = await makeCmdTasks({
8688
commands: task.commands,
8789
files: task.fileList,
8890
gitDir,
@@ -93,7 +95,7 @@ module.exports = async function runAll(
9395
hasDeprecatedGitAdd = true
9496
}
9597

96-
return {
98+
chunkListrTasks.push({
9799
title: `Running tasks for ${task.pattern}`,
98100
task: async () =>
99101
new Listr(subTasks, {
@@ -109,8 +111,8 @@ module.exports = async function runAll(
109111
}
110112
return false
111113
}
112-
}
113-
})
114+
})
115+
}
114116

115117
listrTasks.push({
116118
// No need to show number of task chunks when there's only one

test/makeCmdTasks.spec.js

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,13 @@ describe('makeCmdTasks', () => {
5858
})
5959
})
6060

61-
it('should work with function linter returning a string', async () => {
61+
it('should work with function task returning a string', async () => {
6262
const res = await makeCmdTasks({ commands: () => 'test', gitDir, files: ['test.js'] })
6363
expect(res.length).toBe(1)
6464
expect(res[0].title).toEqual('test')
6565
})
6666

67-
it('should work with function linter returning array of string', async () => {
67+
it('should work with function task returning array of string', async () => {
6868
const res = await makeCmdTasks({
6969
commands: () => ['test', 'test2'],
7070
gitDir,
@@ -75,7 +75,7 @@ describe('makeCmdTasks', () => {
7575
expect(res[1].title).toEqual('test2')
7676
})
7777

78-
it('should work with function linter accepting arguments', async () => {
78+
it('should work with function task accepting arguments', async () => {
7979
const res = await makeCmdTasks({
8080
commands: filenames => filenames.map(file => `test ${file}`),
8181
gitDir,
@@ -86,7 +86,7 @@ describe('makeCmdTasks', () => {
8686
expect(res[1].title).toEqual('test [file]')
8787
})
8888

89-
it('should work with array of mixed string and function linters', async () => {
89+
it('should work with array of mixed string and function tasks', async () => {
9090
const res = await makeCmdTasks({
9191
commands: [() => 'test', 'test2', files => files.map(file => `test ${file}`)],
9292
gitDir,
@@ -109,4 +109,10 @@ describe('makeCmdTasks', () => {
109109
expect(res.length).toBe(1)
110110
expect(res[0].title).toEqual('test --file [file]')
111111
})
112+
113+
it('should work with async function tasks', async () => {
114+
const res = await makeCmdTasks({ commands: async () => 'test', gitDir, files: ['test.js'] })
115+
expect(res.length).toBe(1)
116+
expect(res[0].title).toEqual('test')
117+
})
112118
})

0 commit comments

Comments
 (0)