UNPKG

3.17 kBJavaScriptView Raw
1import debug from 'debug'
2
3import { configurationError } from './messages.js'
4import { ConfigEmptyError, ConfigFormatError } from './symbols.js'
5import { validateBraces } from './validateBraces.js'
6
7const debugLog = debug('lint-staged:validateConfig')
8
9const isObject = (test) => test && typeof test === 'object' && !Array.isArray(test)
10
11const TEST_DEPRECATED_KEYS = new Map([
12 ['concurrent', (key) => typeof key === 'boolean'],
13 ['chunkSize', (key) => typeof key === 'number'],
14 ['globOptions', isObject],
15 ['linters', isObject],
16 ['ignore', (key) => Array.isArray(key)],
17 ['subTaskConcurrency', (key) => typeof key === 'number'],
18 ['renderer', (key) => typeof key === 'string'],
19 ['relative', (key) => typeof key === 'boolean'],
20])
21
22/**
23 * Runs config validation. Throws error if the config is not valid.
24 * @param config {Object}
25 * @returns config {Object}
26 */
27export const validateConfig = (config, logger) => {
28 debugLog('Validating config')
29
30 if (!config || (typeof config !== 'object' && typeof config !== 'function')) {
31 throw ConfigFormatError
32 }
33
34 /**
35 * Function configurations receive all staged files as their argument.
36 * They are not further validated here to make sure the function gets
37 * evaluated only once.
38 *
39 * @see makeCmdTasks
40 */
41 if (typeof config === 'function') {
42 return { '*': config }
43 }
44
45 if (Object.entries(config).length === 0) {
46 throw ConfigEmptyError
47 }
48
49 const errors = []
50
51 /**
52 * Create a new validated config because the keys (patterns) might change.
53 * Since the Object.reduce method already loops through each entry in the config,
54 * it can be used for validating the values at the same time.
55 */
56 const validatedConfig = Object.entries(config).reduce((collection, [pattern, task]) => {
57 /** Versions < 9 had more complex configuration options that are no longer supported. */
58 if (TEST_DEPRECATED_KEYS.has(pattern)) {
59 const testFn = TEST_DEPRECATED_KEYS.get(pattern)
60 if (testFn(task)) {
61 errors.push(
62 configurationError(pattern, 'Advanced configuration has been deprecated.', task)
63 )
64 }
65
66 /** Return early for deprecated keys to skip validating their (deprecated) values */
67 return collection
68 }
69
70 if (
71 (!Array.isArray(task) ||
72 task.some((item) => typeof item !== 'string' && typeof item !== 'function')) &&
73 typeof task !== 'string' &&
74 typeof task !== 'function'
75 ) {
76 errors.push(
77 configurationError(
78 pattern,
79 'Should be a string, a function, or an array of strings and functions.',
80 task
81 )
82 )
83 }
84
85 /**
86 * A typical configuration error is using invalid brace expansion, like `*.{js}`.
87 * These are automatically fixed and warned about.
88 */
89 const fixedPattern = validateBraces(pattern, logger)
90
91 return { ...collection, [fixedPattern]: task }
92 }, {})
93
94 if (errors.length) {
95 const message = errors.join('\n\n')
96
97 logger.error(`Could not parse lint-staged config.
98
99${message}
100
101See https://github.com/okonet/lint-staged#configuration.`)
102
103 throw new Error(message)
104 }
105
106 return validatedConfig
107}