-
-
Notifications
You must be signed in to change notification settings - Fork 425
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Chunked execution of linters on Windows only (#439)
* feat: Chunked execution of linters on Windows only * test: Add tests for resolveTaskFn Move some tests from makeCmdTasks to resolveTaskFn. * chore: Add babel-plugin-transform-object-rest-spread
- Loading branch information
1 parent
0924e78
commit 1601c02
Showing
18 changed files
with
444 additions
and
326 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,5 +5,6 @@ | |
"node": 6 | ||
} | ||
}] | ||
] | ||
], | ||
"plugins": ["transform-object-rest-spread"] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
'use strict' | ||
|
||
const resolveTaskFn = require('./resolveTaskFn') | ||
const resolveGitDir = require('./resolveGitDir') | ||
|
||
const debug = require('debug')('lint-staged:make-cmd-tasks') | ||
|
||
/** | ||
* Creates and returns an array of listr tasks which map to the given commands. | ||
* | ||
* @param {Array<string>|string} commands | ||
* @param {Array<string>} pathsToLint | ||
* @param {Object} [options] | ||
* @param {number} options.chunkSize | ||
* @param {number} options.subTaskConcurrency | ||
*/ | ||
module.exports = function makeCmdTasks( | ||
commands, | ||
pathsToLint, | ||
{ chunkSize = Number.MAX_SAFE_INTEGER, subTaskConcurrency = 1 } = {} | ||
) { | ||
debug('Creating listr tasks for commands %o', commands) | ||
|
||
const gitDir = resolveGitDir() | ||
const lintersArray = Array.isArray(commands) ? commands : [commands] | ||
|
||
return lintersArray.map(linter => ({ | ||
title: linter, | ||
task: resolveTaskFn({ | ||
linter, | ||
gitDir, | ||
pathsToLint, | ||
chunkSize, | ||
subTaskConcurrency | ||
}) | ||
})) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
'use strict' | ||
|
||
const chunk = require('lodash/chunk') | ||
const dedent = require('dedent') | ||
const isWindows = require('is-windows') | ||
const execa = require('execa') | ||
const symbols = require('log-symbols') | ||
const pMap = require('p-map') | ||
const calcChunkSize = require('./calcChunkSize') | ||
const findBin = require('./findBin') | ||
|
||
const debug = require('debug')('lint-staged:task') | ||
|
||
/** | ||
* Execute the given linter binary with arguments and file paths using execa and | ||
* return the promise. | ||
* | ||
* @param {string} bin | ||
* @param {Array<string>} args | ||
* @param {Object} execaOptions | ||
* @param {Array<string>} pathsToLint | ||
* @return {Promise} | ||
*/ | ||
function execLinter(bin, args, execaOptions, pathsToLint) { | ||
const binArgs = args.concat(pathsToLint) | ||
|
||
debug('bin:', bin) | ||
debug('args: %O', binArgs) | ||
debug('opts: %o', execaOptions) | ||
|
||
return execa(bin, binArgs, Object.assign({}, execaOptions)) | ||
} | ||
|
||
const successMsg = linter => `${symbols.success} ${linter} passed!` | ||
|
||
/** | ||
* Create and returns an error instance with given stdout and stderr. If we set | ||
* the message on the error instance, it gets logged multiple times(see #142). | ||
* So we set the actual error message in a private field and extract it later, | ||
* log only once. | ||
* | ||
* @param {string} linter | ||
* @param {string} errStdout | ||
* @param {string} errStderr | ||
* @returns {Error} | ||
*/ | ||
function makeErr(linter, errStdout, errStderr) { | ||
const err = new Error() | ||
err.privateMsg = dedent` | ||
${symbols.error} "${linter}" found some errors. Please fix them and try committing again. | ||
${errStdout} | ||
${errStderr} | ||
` | ||
return err | ||
} | ||
|
||
/** | ||
* Returns the task function for the linter. It handles chunking for file paths | ||
* if the OS is Windows. | ||
* | ||
* @param {Object} options | ||
* @param {string} options.linter | ||
* @param {string} options.gitDir | ||
* @param {Array<string>} options.pathsToLint | ||
* @param {number} options.chunkSize | ||
* @param {number} options.subTaskConcurrency | ||
* @returns {function(): Promise<string>} | ||
*/ | ||
module.exports = function resolveTaskFn(options) { | ||
const { linter, gitDir, pathsToLint } = options | ||
const { bin, args } = findBin(linter) | ||
|
||
const execaOptions = { reject: false } | ||
// Only use gitDir as CWD if we are using the git binary | ||
// e.g `npm` should run tasks in the actual CWD | ||
if (/git(\.exe)?$/i.test(bin) && gitDir !== process.cwd()) { | ||
execaOptions.cwd = gitDir | ||
} | ||
|
||
if (!isWindows()) { | ||
debug('%s OS: %s; File path chunking unnecessary', symbols.success, process.platform) | ||
return () => | ||
execLinter(bin, args, execaOptions, pathsToLint).then(result => { | ||
if (!result.failed) return successMsg(linter) | ||
|
||
throw makeErr(linter, result.stdout, result.stderr) | ||
}) | ||
} | ||
|
||
const { chunkSize, subTaskConcurrency: concurrency } = options | ||
|
||
const filePathChunks = chunk(pathsToLint, calcChunkSize(pathsToLint, chunkSize)) | ||
const mapper = execLinter.bind(null, bin, args, execaOptions) | ||
|
||
debug( | ||
'OS: %s; Creating linter task with %d chunked file paths', | ||
process.platform, | ||
filePathChunks.length | ||
) | ||
return () => | ||
pMap(filePathChunks, mapper, { concurrency }) | ||
.catch(err => { | ||
/* This will probably never be called. But just in case.. */ | ||
throw new Error(dedent` | ||
${symbols.error} ${linter} got an unexpected error. | ||
${err.message} | ||
`) | ||
}) | ||
.then(results => { | ||
const errors = results.filter(res => res.failed) | ||
if (errors.length === 0) return successMsg(linter) | ||
|
||
const errStdout = errors.map(err => err.stdout).join('') | ||
const errStderr = errors.map(err => err.stderr).join('') | ||
|
||
throw makeErr(linter, errStdout, errStderr) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import execa from 'execa' | ||
import makeCmdTasks from '../src/makeCmdTasks' | ||
|
||
describe('makeCmdTasks', () => { | ||
beforeEach(() => { | ||
execa.mockClear() | ||
}) | ||
|
||
it('should return an array', () => { | ||
expect(makeCmdTasks('test', ['test.js'])).toBeInstanceOf(Array) | ||
}) | ||
|
||
it('should work with a single command', async () => { | ||
expect.assertions(4) | ||
const res = makeCmdTasks('test', ['test.js']) | ||
expect(res.length).toBe(1) | ||
const [linter] = res | ||
expect(linter.title).toBe('test') | ||
expect(linter.task).toBeInstanceOf(Function) | ||
const taskPromise = linter.task() | ||
expect(taskPromise).toBeInstanceOf(Promise) | ||
await taskPromise | ||
}) | ||
|
||
it('should work with multiple commands', async () => { | ||
expect.assertions(9) | ||
const res = makeCmdTasks(['test', 'test2'], ['test.js']) | ||
expect(res.length).toBe(2) | ||
const [linter1, linter2] = res | ||
expect(linter1.title).toBe('test') | ||
expect(linter2.title).toBe('test2') | ||
|
||
let taskPromise = linter1.task() | ||
expect(taskPromise).toBeInstanceOf(Promise) | ||
await taskPromise | ||
expect(execa).toHaveBeenCalledTimes(1) | ||
expect(execa).lastCalledWith('test', ['test.js'], { reject: false }) | ||
taskPromise = linter2.task() | ||
expect(taskPromise).toBeInstanceOf(Promise) | ||
await taskPromise | ||
expect(execa).toHaveBeenCalledTimes(2) | ||
expect(execa).lastCalledWith('test2', ['test.js'], { reject: false }) | ||
}) | ||
}) |
Oops, something went wrong.