Skip to content

Commit

Permalink
refactor: convert main entrypoint to use an object
Browse files Browse the repository at this point in the history
  • Loading branch information
cameronhunter authored and okonet committed Jul 6, 2019
1 parent d8b3695 commit 386ff4a
Show file tree
Hide file tree
Showing 9 changed files with 85 additions and 67 deletions.
7 changes: 6 additions & 1 deletion bin/lint-staged
Expand Up @@ -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
},
Expand Down
35 changes: 17 additions & 18 deletions src/index.js
Expand Up @@ -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')

Expand Down Expand Up @@ -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<number>} 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 => {
Expand Down
1 change: 0 additions & 1 deletion src/makeCmdTasks.js
Expand Up @@ -10,7 +10,6 @@ const debug = require('debug')('lint-staged:make-cmd-tasks')
* @param {Array<string|Function>|string|Function} commands
* @param {Boolean} shell
* @param {Array<string>} pathsToLint
* @param {Object} [options]
*/
module.exports = async function makeCmdTasks(commands, shell, gitDir, pathsToLint) {
debug('Creating listr tasks for commands %o', commands)
Expand Down
6 changes: 3 additions & 3 deletions src/printErrors.js
Expand Up @@ -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))
}
}
2 changes: 1 addition & 1 deletion src/resolveTaskFn.js
Expand Up @@ -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<string>} options.pathsToLint
* @param {Boolean} [options.shell]
* @returns {function(): Promise<Array<string>>}
*/
module.exports = function resolveTaskFn({ gitDir, isFn, linter, pathsToLint, shell = false }) {
Expand Down
17 changes: 11 additions & 6 deletions 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')
Expand All @@ -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')

Expand All @@ -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:
Expand Down Expand Up @@ -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.'
}

Expand Down
25 changes: 20 additions & 5 deletions test/__snapshots__/index.spec.js.snap
@@ -1,29 +1,43 @@
// 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`] = `
"
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`] = `
Expand All @@ -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`] = `
Expand Down
42 changes: 20 additions & 22 deletions test/index.spec.js
Expand Up @@ -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
Expand Down Expand Up @@ -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()
})

Expand All @@ -53,52 +49,54 @@ 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()
})

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()
})

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)
})
Expand All @@ -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)
})
Expand All @@ -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)
})
})
17 changes: 7 additions & 10 deletions test/index2.spec.js
Expand Up @@ -18,23 +18,20 @@ 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' })
})

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' })
})
Expand Down

0 comments on commit 386ff4a

Please sign in to comment.