-
-
Notifications
You must be signed in to change notification settings - Fork 412
/
validateConfig.js
147 lines (121 loc) Β· 4.4 KB
/
validateConfig.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
/* eslint no-prototype-builtins: 0 */
'use strict'
const debug = require('debug')('lint-staged:cfg')
const { configurationError, incorrectBraces } = require('./messages')
const TEST_DEPRECATED_KEYS = new Map([
['concurrent', (key) => typeof key === 'boolean'],
['chunkSize', (key) => typeof key === 'number'],
['globOptions', (key) => typeof key === 'object'],
['linters', (key) => typeof key === 'object'],
['ignore', (key) => Array.isArray(key)],
['subTaskConcurrency', (key) => typeof key === 'number'],
['renderer', (key) => typeof key === 'string'],
['relative', (key) => typeof key === 'boolean'],
])
/**
* Braces with a single value like `*.{js}` are invalid
* and thus ignored by micromatch. This regex matches all occurrences of
* two curly braces without a `,` or `..` between them, to make sure
* users can still accidentally use them without
* some linters never matching anything. It will further not match
* escaped braces `\{` `\}`, or if braces contain an escaped comma `\,`.
* Finally, a dollar sign `${` inhibits brace expansion.
*
* For example `.{js,ts}` or `file_{1..10}` are valid but `*.{js}` is not.
*
* @see https://www.gnu.org/software/bash/manual/html_node/Brace-Expansion.html
*/
const BRACES_REGEXP = new RegExp(/(?<![\\$])({)(?:(?!(?<!\\),|\.\.).)*?(?<!\\)(})/g)
/**
* Remove braces from incorrect glob patterns.
* For example `*.{js}` is incorrect because it doesn't contain a `,` or `..`,
* and will be reformatted as `*.js`.
*
* @param {string} pattern the glob pattern
* @returns {string}
*/
const withoutIncorrectBraces = (pattern) => {
let output = `${pattern}`
while (BRACES_REGEXP.exec(pattern)) {
output = output.replace(/{/, '')
output = output.replace(/}/, '')
}
BRACES_REGEXP.lastIndex = 0 /** Reset lastIndex */
return output
}
/**
* Runs config validation. Throws error if the config is not valid.
* @param config {Object}
* @returns config {Object}
*/
const validateConfig = (config, logger) => {
debug('Validating config')
if (!config || (typeof config !== 'object' && typeof config !== 'function')) {
throw new Error('Configuration should be an object or a function!')
}
/**
* Function configurations receive all staged files as their argument.
* They are not further validated here to make sure the function gets
* evaluated only once.
*
* @see makeCmdTasks
*/
if (typeof config === 'function') {
return { '*': config }
}
if (Object.entries(config).length === 0) {
throw new Error('Configuration should not be empty!')
}
const errors = []
/**
* Create a new validated config because the keys (patterns) might change.
* Since the Object.reduce method already loops through each entry in the config,
* it can be used for validating the values at the same time.
*/
const validatedConfig = Object.entries(config).reduce((collection, [pattern, task]) => {
/** Versions < 9 had more complex configuration options that are no longer supported. */
if (TEST_DEPRECATED_KEYS.has(pattern)) {
const testFn = TEST_DEPRECATED_KEYS.get(pattern)
if (testFn(task)) {
errors.push(
configurationError(pattern, 'Advanced configuration has been deprecated.', task)
)
}
/** Return early for deprecated keys to skip validating their (deprecated) values */
return collection
}
if (
(!Array.isArray(task) ||
task.some((item) => typeof item !== 'string' && typeof item !== 'function')) &&
typeof task !== 'string' &&
typeof task !== 'function'
) {
errors.push(
configurationError(
pattern,
'Should be a string, a function, or an array of strings and functions.',
task
)
)
}
/**
* A typical configuration error is using invalid brace expansion, like `*.{js}`.
* These are automatically fixed and warned about.
*/
const fixedPattern = withoutIncorrectBraces(pattern)
if (fixedPattern !== pattern) {
logger.warn(incorrectBraces(pattern, fixedPattern))
}
return { ...collection, [fixedPattern]: task }
}, {})
if (errors.length) {
const message = errors.join('\n\n')
logger.error(`Could not parse lint-staged config.
${message}
See https://github.com/okonet/lint-staged#configuration.`)
throw new Error(message)
}
return validatedConfig
}
module.exports = validateConfig
module.exports.BRACES_REGEXP = BRACES_REGEXP