diff --git a/bin/lint-staged b/bin/lint-staged index 8a5f9c751..9ba9551df 100755 --- a/bin/lint-staged +++ b/bin/lint-staged @@ -38,7 +38,12 @@ if (cmdline.debug) { debug('Running `lint-staged@%s`', pkg.version) -lintStaged(console, cmdline.config, !!cmdline.shell, !!cmdline.quiet, !!cmdline.debug).then( +lintStaged({ + configPath: cmdline.config, + shell: !!cmdline.shell, + quiet: !!cmdline.quiet, + debug: !!cmdline.debug +}).then( exitCode => { process.exitCode = exitCode }, diff --git a/src/index.js b/src/index.js index 4d01fa1b5..2673da278 100644 --- a/src/index.js +++ b/src/index.js @@ -7,7 +7,7 @@ const printErrors = require('./printErrors') const runAll = require('./runAll') const validateConfig = require('./validateConfig') -const debug = require('debug')('lint-staged') +const debugLog = require('debug')('lint-staged') const errConfigNotFound = new Error('Config could not be found') @@ -37,47 +37,46 @@ function loadConfig(configPath) { /** * @typedef {(...any) => void} LogFunction - * @typedef {{ error: LogFunction, log: LogFunction }} Logger + * @typedef {{ error: LogFunction, log: LogFunction, warn: LogFunction }} Logger * * Root lint-staged function that is called from `bin/lint-staged`. * - * @param {Logger} logger - * @param {String} configPath - * @param {Boolean} shellMode Use execa’s shell mode to execute linter commands - * @param {Boolean} quietMode Use Listr’s silent renderer - * @param {Boolean} debugMode Enable debug mode + * @param {object} options + * @param {string} [options.configPath] - Path to configuration file + * @param {boolean} [options.shell] - Use execa’s shell mode to execute linter commands + * @param {boolean} [options.quiet] - Use Listr’s silent renderer + * @param {boolean} [options.debug] - Enable debug mode + * @param {Logger} [logger] + * * @returns {Promise} Promise containing the exit code to use */ module.exports = function lintStaged( - logger = console, - configPath, - shellMode = false, - quietMode = false, - debugMode = false + { configPath, shell = false, quiet = false, debug = false } = {}, + logger = console ) { - debug('Loading config using `cosmiconfig`') + debugLog('Loading config using `cosmiconfig`') return loadConfig(configPath) .then(result => { if (result == null) throw errConfigNotFound - debug('Successfully loaded config from `%s`:\n%O', result.filepath, result.config) + debugLog('Successfully loaded config from `%s`:\n%O', result.filepath, result.config) // result.config is the parsed configuration object // result.filepath is the path to the config file that was found const config = validateConfig(result.config) - if (debugMode) { + if (debug) { // Log using logger to be able to test through `consolemock`. logger.log('Running lint-staged with the following config:') logger.log(stringifyObject(config, { indent: ' ' })) } else { // We might not be in debug mode but `DEBUG=lint-staged*` could have // been set. - debug('Normalized config:\n%O', config) + debugLog('Normalized config:\n%O', config) } - return runAll(config, shellMode, quietMode, debugMode) + return runAll(config, shell, quiet, debug, logger) .then(() => { - debug('linters were executed successfully!') + debugLog('linters were executed successfully!') return Promise.resolve(0) }) .catch(error => { diff --git a/src/makeCmdTasks.js b/src/makeCmdTasks.js index 8ac8d1882..d07ec66a4 100644 --- a/src/makeCmdTasks.js +++ b/src/makeCmdTasks.js @@ -10,7 +10,6 @@ const debug = require('debug')('lint-staged:make-cmd-tasks') * @param {Array|string|Function} commands * @param {Boolean} shell * @param {Array} pathsToLint - * @param {Object} [options] */ module.exports = async function makeCmdTasks(commands, shell, gitDir, pathsToLint) { debug('Creating listr tasks for commands %o', commands) diff --git a/src/printErrors.js b/src/printErrors.js index adf5c0132..61c716113 100644 --- a/src/printErrors.js +++ b/src/printErrors.js @@ -4,12 +4,12 @@ // Work-around for duplicated error logs, see #142 const errMsg = err => (err.privateMsg != null ? err.privateMsg : err.message) -module.exports = function printErrors(errorInstance) { +module.exports = function printErrors(errorInstance, logger = console) { if (Array.isArray(errorInstance.errors)) { errorInstance.errors.forEach(lintError => { - console.error(errMsg(lintError)) + logger.error(errMsg(lintError)) }) } else { - console.error(errMsg(errorInstance)) + logger.error(errMsg(errorInstance)) } } diff --git a/src/resolveTaskFn.js b/src/resolveTaskFn.js index b6b52d9ae..6b0410243 100644 --- a/src/resolveTaskFn.js +++ b/src/resolveTaskFn.js @@ -80,8 +80,8 @@ function makeErr(linter, result, context = {}) { * @param {string} options.gitDir * @param {Boolean} options.isFn * @param {string} options.linter - * @param {Boolean} options.shellMode * @param {Array} options.pathsToLint + * @param {Boolean} [options.shell] * @returns {function(): Promise>} */ module.exports = function resolveTaskFn({ gitDir, isFn, linter, pathsToLint, shell = false }) { diff --git a/src/runAll.js b/src/runAll.js index 13ec747da..097e6fba8 100644 --- a/src/runAll.js +++ b/src/runAll.js @@ -1,5 +1,7 @@ 'use strict' +/** @typedef {import('./index').Logger} Logger */ + const chalk = require('chalk') const dedent = require('dedent') const Listr = require('listr') @@ -23,17 +25,20 @@ const MAX_ARG_LENGTH = /** * Executes all tasks and either resolves or rejects the promise + * * @param config {Object} - * @param {Boolean} shellMode Use execa’s shell mode to execute linter commands - * @param {Boolean} quietMode Use Listr’s silent renderer - * @param {Boolean} debugMode Enable debug mode + * @param {Boolean} [shellMode] Use execa’s shell mode to execute linter commands + * @param {Boolean} [quietMode] Use Listr’s silent renderer + * @param {Boolean} [debugMode] Enable debug mode + * @param {Logger} [logger] * @returns {Promise} */ module.exports = async function runAll( config, shellMode = false, quietMode = false, - debugMode = false + debugMode = false, + logger = console ) { debug('Running all linter scripts') @@ -55,7 +60,7 @@ module.exports = async function runAll( const argLength = files.join(' ').length if (argLength > MAX_ARG_LENGTH) { - console.warn( + logger.warn( dedent`${symbols.warning} ${chalk.yellow( `lint-staged generated an argument string of ${argLength} characters, and commands might not run correctly on your platform. It is recommended to use functions as linters and split your command based on the number of staged files. For more info, please visit: @@ -91,7 +96,7 @@ https://github.com/okonet/lint-staged#using-js-functions-to-customize-linter-com // If all of the configured "linters" should be skipped // avoid executing any lint-staged logic if (tasks.every(task => task.skip())) { - console.log('No staged files match any of provided globs.') + logger.log('No staged files match any of provided globs.') return 'No tasks to run.' } diff --git a/test/__snapshots__/index.spec.js.snap b/test/__snapshots__/index.spec.js.snap index 975ff6b64..552008507 100644 --- a/test/__snapshots__/index.spec.js.snap +++ b/test/__snapshots__/index.spec.js.snap @@ -1,11 +1,20 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`lintStaged should exit with code 1 on linter errors 1`] = ` +" +ERROR + + +× node -e \\"process.exit(1)\\" found some errors. Please fix them and try committing again." +`; + exports[`lintStaged should load an npm config package when specified 1`] = ` " LOG Running lint-staged with the following config: LOG { '*': 'mytask' -}" +} +ERROR Unable to get staged files!" `; exports[`lintStaged should load config file when specified 1`] = ` @@ -13,17 +22,22 @@ exports[`lintStaged should load config file when specified 1`] = ` LOG Running lint-staged with the following config: LOG { '*': 'mytask' -}" +} +ERROR Unable to get staged files!" `; -exports[`lintStaged should not output config in normal mode 1`] = `""`; +exports[`lintStaged should not output config in normal mode 1`] = ` +" +ERROR Unable to get staged files!" +`; exports[`lintStaged should output config in debug mode 1`] = ` " LOG Running lint-staged with the following config: LOG { '*': 'mytask' -}" +} +ERROR Unable to get staged files!" `; exports[`lintStaged should parse function linter from js config 1`] = ` @@ -32,7 +46,8 @@ LOG Running lint-staged with the following config: LOG { '*.css': filenames => \`echo \${filenames.join(' ')}\`, '*.js': filenames => filenames.map(filename => \`echo \${filename}\`) -}" +} +ERROR Unable to get staged files!" `; exports[`lintStaged should print helpful error message when config file is not found 1`] = ` diff --git a/test/index.spec.js b/test/index.spec.js index be0163208..05c9f8bf7 100644 --- a/test/index.spec.js +++ b/test/index.spec.js @@ -4,10 +4,6 @@ import path from 'path' jest.unmock('execa') -// silence console from Jest output -console.log = jest.fn(() => {}) -console.error = jest.fn(() => {}) - // eslint-disable-next-line import/first import getStagedFiles from '../src/getStagedFiles' // eslint-disable-next-line import/first @@ -43,7 +39,7 @@ describe('lintStaged', () => { '*': 'mytask' } mockCosmiconfigWith({ config }) - await lintStaged(logger, undefined, false, false, true) + await lintStaged({ debug: true, quiet: true }, logger) expect(logger.printHistory()).toMatchSnapshot() }) @@ -53,25 +49,26 @@ describe('lintStaged', () => { '*': 'mytask' } mockCosmiconfigWith({ config }) - await lintStaged(logger) + await lintStaged({ quiet: true }, logger) expect(logger.printHistory()).toMatchSnapshot() }) it('should throw when invalid config is provided', async () => { const config = {} mockCosmiconfigWith({ config }) - await lintStaged(logger) + await lintStaged({ quiet: true }, logger) expect(logger.printHistory()).toMatchSnapshot() }) it('should load config file when specified', async () => { expect.assertions(1) await lintStaged( - logger, - path.join(__dirname, '__mocks__', 'my-config.json'), - false, - false, - true + { + configPath: path.join(__dirname, '__mocks__', 'my-config.json'), + debug: true, + quiet: true + }, + logger ) expect(logger.printHistory()).toMatchSnapshot() }) @@ -79,11 +76,12 @@ describe('lintStaged', () => { it('should parse function linter from js config', async () => { expect.assertions(1) await lintStaged( - logger, - path.join(__dirname, '__mocks__', 'advanced-config.js'), - false, - false, - true + { + configPath: path.join(__dirname, '__mocks__', 'advanced-config.js'), + debug: true, + quiet: true + }, + logger ) expect(logger.printHistory()).toMatchSnapshot() }) @@ -91,14 +89,14 @@ describe('lintStaged', () => { it('should load an npm config package when specified', async () => { expect.assertions(1) jest.mock('my-lint-staged-config') - await lintStaged(logger, 'my-lint-staged-config', false, false, true) + await lintStaged({ configPath: 'my-lint-staged-config', quiet: true, debug: true }, logger) expect(logger.printHistory()).toMatchSnapshot() }) it('should print helpful error message when config file is not found', async () => { expect.assertions(2) mockCosmiconfigWith(null) - const exitCode = await lintStaged(logger) + const exitCode = await lintStaged({ quiet: true }, logger) expect(logger.printHistory()).toMatchSnapshot() expect(exitCode).toEqual(1) }) @@ -115,7 +113,7 @@ describe('lintStaged', () => { ) ) - const exitCode = await lintStaged(logger, nonExistentConfig) + const exitCode = await lintStaged({ configPath: nonExistentConfig, quiet: true }, logger) expect(logger.printHistory()).toMatchSnapshot() expect(exitCode).toEqual(1) }) @@ -126,8 +124,8 @@ describe('lintStaged', () => { } mockCosmiconfigWith({ config }) getStagedFiles.mockImplementationOnce(async () => ['sample.java']) - const exitCode = await lintStaged(logger, undefined) - expect(console.error).toHaveBeenCalledWith(expect.stringContaining('node -e "process.exit(1)"')) + const exitCode = await lintStaged({ quiet: true }, logger) + expect(logger.printHistory()).toMatchSnapshot() expect(exitCode).toEqual(1) }) }) diff --git a/test/index2.spec.js b/test/index2.spec.js index eacd7e265..1cff7330b 100644 --- a/test/index2.spec.js +++ b/test/index2.spec.js @@ -18,11 +18,8 @@ describe('lintStaged', () => { it('should pass quiet flag to Listr', async () => { expect.assertions(1) await lintStaged( - console, - path.join(__dirname, '__mocks__', 'my-config.json'), - false, - true, - false + { configPath: path.join(__dirname, '__mocks__', 'my-config.json'), quiet: true }, + console ) expect(Listr.mock.calls[0][1]).toEqual({ dateFormat: false, renderer: 'silent' }) }) @@ -30,11 +27,11 @@ describe('lintStaged', () => { it('should pass debug flag to Listr', async () => { expect.assertions(1) await lintStaged( - console, - path.join(__dirname, '__mocks__', 'my-config.json'), - false, - false, - true + { + configPath: path.join(__dirname, '__mocks__', 'my-config.json'), + debug: true + }, + console ) expect(Listr.mock.calls[0][1]).toEqual({ dateFormat: false, renderer: 'verbose' }) })