1 |
|
2 |
|
3 | 'use strict'
|
4 |
|
5 | const chalk = require('chalk')
|
6 | const format = require('stringify-object')
|
7 | const intersection = require('lodash/intersection')
|
8 | const defaultsDeep = require('lodash/defaultsDeep')
|
9 | const isArray = require('lodash/isArray')
|
10 | const isFunction = require('lodash/isFunction')
|
11 | const isObject = require('lodash/isObject')
|
12 | const isString = require('lodash/isString')
|
13 | const isGlob = require('is-glob')
|
14 | const yup = require('yup')
|
15 |
|
16 | const debug = require('debug')('lint-staged:cfg')
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 | const defaultConfig = {
|
24 | concurrent: true,
|
25 | chunkSize: Number.MAX_SAFE_INTEGER,
|
26 | globOptions: {
|
27 | matchBase: true,
|
28 | dot: true
|
29 | },
|
30 | linters: {},
|
31 | ignore: [],
|
32 | subTaskConcurrency: 1,
|
33 | renderer: 'update',
|
34 | relative: false
|
35 | }
|
36 |
|
37 |
|
38 |
|
39 |
|
40 | const schema = yup.object().shape({
|
41 | concurrent: yup.boolean().default(defaultConfig.concurrent),
|
42 | chunkSize: yup
|
43 | .number()
|
44 | .positive()
|
45 | .default(defaultConfig.chunkSize),
|
46 | globOptions: yup.object().shape({
|
47 | matchBase: yup.boolean().default(defaultConfig.globOptions.matchBase),
|
48 | dot: yup.boolean().default(defaultConfig.globOptions.dot)
|
49 | }),
|
50 | linters: yup.object(),
|
51 | ignore: yup.array().of(yup.string()),
|
52 | subTaskConcurrency: yup
|
53 | .number()
|
54 | .positive()
|
55 | .integer()
|
56 | .default(defaultConfig.subTaskConcurrency),
|
57 | renderer: yup
|
58 | .mixed()
|
59 | .test(
|
60 | 'renderer',
|
61 | "Should be 'update', 'verbose' or a function.",
|
62 | value => value === 'update' || value === 'verbose' || isFunction(value)
|
63 | ),
|
64 | relative: yup.boolean().default(defaultConfig.relative)
|
65 | })
|
66 |
|
67 |
|
68 |
|
69 |
|
70 |
|
71 |
|
72 |
|
73 | function isSimple(config) {
|
74 | return (
|
75 | isObject(config) &&
|
76 | !config.hasOwnProperty('linters') &&
|
77 | intersection(Object.keys(defaultConfig), Object.keys(config)).length === 0
|
78 | )
|
79 | }
|
80 |
|
81 | const logDeprecation = (opt, helpMsg) =>
|
82 | console.warn(`● Deprecation Warning:
|
83 |
|
84 | Option ${chalk.bold(opt)} was removed.
|
85 |
|
86 | ${helpMsg}
|
87 |
|
88 | Please remove ${chalk.bold(opt)} from your configuration.
|
89 |
|
90 | Please refer to https://github.com/okonet/lint-staged#configuration for more information...`)
|
91 |
|
92 | const logUnknown = (opt, helpMsg, value) =>
|
93 | console.warn(`● Validation Warning:
|
94 |
|
95 | Unknown option ${chalk.bold(`"${opt}"`)} with value ${chalk.bold(
|
96 | format(value, { inlineCharacterLimit: Number.POSITIVE_INFINITY })
|
97 | )} was found in the config root.
|
98 |
|
99 | ${helpMsg}
|
100 |
|
101 | Please refer to https://github.com/okonet/lint-staged#configuration for more information...`)
|
102 |
|
103 | const formatError = helpMsg => `● Validation Error:
|
104 |
|
105 | ${helpMsg}
|
106 |
|
107 | Please refer to https://github.com/okonet/lint-staged#configuration for more information...`
|
108 |
|
109 | const createError = (opt, helpMsg, value) =>
|
110 | formatError(`Invalid value for '${chalk.bold(opt)}'.
|
111 |
|
112 | ${helpMsg}.
|
113 |
|
114 | Configured value is: ${chalk.bold(
|
115 | format(value, { inlineCharacterLimit: Number.POSITIVE_INFINITY })
|
116 | )}`)
|
117 |
|
118 |
|
119 |
|
120 |
|
121 |
|
122 |
|
123 |
|
124 | function unknownValidationReporter(config, option) {
|
125 | |
126 |
|
127 |
|
128 |
|
129 | if (isGlob(option)) {
|
130 |
|
131 | const message = `You are probably trying to mix simple and advanced config formats. Adding
|
132 |
|
133 | ${chalk.bold(`"linters": {
|
134 | "${option}": ${JSON.stringify(config[option])}
|
135 | }`)}
|
136 |
|
137 | will fix it and remove this message.`
|
138 |
|
139 | return logUnknown(option, message, config[option])
|
140 | }
|
141 |
|
142 |
|
143 | return logUnknown(option, '', config[option])
|
144 | }
|
145 |
|
146 |
|
147 |
|
148 |
|
149 |
|
150 |
|
151 |
|
152 |
|
153 |
|
154 |
|
155 |
|
156 |
|
157 |
|
158 | function getConfig(sourceConfig, debugMode) {
|
159 | debug('Normalizing config')
|
160 | const config = defaultsDeep(
|
161 | {},
|
162 | isSimple(sourceConfig) ? { linters: sourceConfig } : sourceConfig,
|
163 | defaultConfig
|
164 | )
|
165 |
|
166 |
|
167 | if (isObject(sourceConfig) && !sourceConfig.hasOwnProperty('renderer')) {
|
168 | config.renderer = debugMode ? 'verbose' : 'update'
|
169 | }
|
170 |
|
171 | return config
|
172 | }
|
173 |
|
174 |
|
175 |
|
176 |
|
177 |
|
178 |
|
179 | function validateConfig(config) {
|
180 | debug('Validating config')
|
181 |
|
182 | const deprecatedConfig = {
|
183 | gitDir: "lint-staged now automatically resolves '.git' directory.",
|
184 | verbose: `Use the command line flag ${chalk.bold('--debug')} instead.`
|
185 | }
|
186 |
|
187 | const errors = []
|
188 |
|
189 | try {
|
190 | schema.validateSync(config, { abortEarly: false, strict: true })
|
191 | } catch (error) {
|
192 | error.errors.forEach(message => errors.push(formatError(message)))
|
193 | }
|
194 |
|
195 | if (isObject(config.linters)) {
|
196 | Object.keys(config.linters).forEach(key => {
|
197 | if (
|
198 | (!isArray(config.linters[key]) || config.linters[key].some(item => !isString(item))) &&
|
199 | !isString(config.linters[key])
|
200 | ) {
|
201 | errors.push(
|
202 | createError(`linters[${key}]`, 'Should be a string or an array of strings', key)
|
203 | )
|
204 | }
|
205 | })
|
206 | }
|
207 |
|
208 | Object.keys(config)
|
209 | .filter(key => !defaultConfig.hasOwnProperty(key))
|
210 | .forEach(option => {
|
211 | if (deprecatedConfig.hasOwnProperty(option)) {
|
212 | logDeprecation(option, deprecatedConfig[option])
|
213 | return
|
214 | }
|
215 |
|
216 | unknownValidationReporter(config, option)
|
217 | })
|
218 |
|
219 | if (errors.length) {
|
220 | throw new Error(errors.join('\n'))
|
221 | }
|
222 |
|
223 | return config
|
224 | }
|
225 |
|
226 | module.exports = {
|
227 | getConfig,
|
228 | validateConfig
|
229 | }
|