UNPKG

9.88 kBJavaScriptView Raw
1/*
2 MIT License http://www.opensource.org/licenses/mit-license.php
3 Author Gajus Kuizinas @gajus
4*/
5"use strict";
6
7const WebpackError = require("./WebpackError");
8const webpackOptionsSchema = require("../schemas/WebpackOptions.json");
9
10const getSchemaPart = (path, parents, additionalPath) => {
11 parents = parents || 0;
12 path = path.split("/");
13 path = path.slice(0, path.length - parents);
14 if (additionalPath) {
15 additionalPath = additionalPath.split("/");
16 path = path.concat(additionalPath);
17 }
18 let schemaPart = webpackOptionsSchema;
19 for (let i = 1; i < path.length; i++) {
20 const inner = schemaPart[path[i]];
21 if (inner) schemaPart = inner;
22 }
23 return schemaPart;
24};
25
26const getSchemaPartText = (schemaPart, additionalPath) => {
27 if (additionalPath) {
28 for (let i = 0; i < additionalPath.length; i++) {
29 const inner = schemaPart[additionalPath[i]];
30 if (inner) schemaPart = inner;
31 }
32 }
33 while (schemaPart.$ref) {
34 schemaPart = getSchemaPart(schemaPart.$ref);
35 }
36 let schemaText = WebpackOptionsValidationError.formatSchema(schemaPart);
37 if (schemaPart.description) {
38 schemaText += `\n-> ${schemaPart.description}`;
39 }
40 return schemaText;
41};
42
43const getSchemaPartDescription = schemaPart => {
44 while (schemaPart.$ref) {
45 schemaPart = getSchemaPart(schemaPart.$ref);
46 }
47 if (schemaPart.description) {
48 return `\n-> ${schemaPart.description}`;
49 }
50 return "";
51};
52
53const filterChildren = children => {
54 return children.filter(
55 err =>
56 err.keyword !== "anyOf" &&
57 err.keyword !== "allOf" &&
58 err.keyword !== "oneOf"
59 );
60};
61
62const indent = (str, prefix, firstLine) => {
63 if (firstLine) {
64 return prefix + str.replace(/\n(?!$)/g, "\n" + prefix);
65 } else {
66 return str.replace(/\n(?!$)/g, `\n${prefix}`);
67 }
68};
69
70class WebpackOptionsValidationError extends WebpackError {
71 constructor(validationErrors) {
72 super(
73 "Invalid configuration object. " +
74 "Webpack has been initialised using a configuration object that does not match the API schema.\n" +
75 validationErrors
76 .map(
77 err =>
78 " - " +
79 indent(
80 WebpackOptionsValidationError.formatValidationError(err),
81 " ",
82 false
83 )
84 )
85 .join("\n")
86 );
87
88 this.name = "WebpackOptionsValidationError";
89 this.validationErrors = validationErrors;
90
91 Error.captureStackTrace(this, this.constructor);
92 }
93
94 static formatSchema(schema, prevSchemas) {
95 prevSchemas = prevSchemas || [];
96
97 const formatInnerSchema = (innerSchema, addSelf) => {
98 if (!addSelf) {
99 return WebpackOptionsValidationError.formatSchema(
100 innerSchema,
101 prevSchemas
102 );
103 }
104 if (prevSchemas.includes(innerSchema)) {
105 return "(recursive)";
106 }
107 return WebpackOptionsValidationError.formatSchema(
108 innerSchema,
109 prevSchemas.concat(schema)
110 );
111 };
112
113 if (schema.type === "string") {
114 if (schema.minLength === 1) {
115 return "non-empty string";
116 }
117 if (schema.minLength > 1) {
118 return `string (min length ${schema.minLength})`;
119 }
120 return "string";
121 }
122 if (schema.type === "boolean") {
123 return "boolean";
124 }
125 if (schema.type === "number") {
126 return "number";
127 }
128 if (schema.type === "object") {
129 if (schema.properties) {
130 const required = schema.required || [];
131 return `object { ${Object.keys(schema.properties)
132 .map(property => {
133 if (!required.includes(property)) return property + "?";
134 return property;
135 })
136 .concat(schema.additionalProperties ? ["…"] : [])
137 .join(", ")} }`;
138 }
139 if (schema.additionalProperties) {
140 return `object { <key>: ${formatInnerSchema(
141 schema.additionalProperties
142 )} }`;
143 }
144 return "object";
145 }
146 if (schema.type === "array") {
147 return `[${formatInnerSchema(schema.items)}]`;
148 }
149
150 switch (schema.instanceof) {
151 case "Function":
152 return "function";
153 case "RegExp":
154 return "RegExp";
155 }
156
157 if (schema.$ref) {
158 return formatInnerSchema(getSchemaPart(schema.$ref), true);
159 }
160 if (schema.allOf) {
161 return schema.allOf.map(formatInnerSchema).join(" & ");
162 }
163 if (schema.oneOf) {
164 return schema.oneOf.map(formatInnerSchema).join(" | ");
165 }
166 if (schema.anyOf) {
167 return schema.anyOf.map(formatInnerSchema).join(" | ");
168 }
169 if (schema.enum) {
170 return schema.enum.map(item => JSON.stringify(item)).join(" | ");
171 }
172 return JSON.stringify(schema, null, 2);
173 }
174
175 static formatValidationError(err) {
176 const dataPath = `configuration${err.dataPath}`;
177 if (err.keyword === "additionalProperties") {
178 const baseMessage = `${dataPath} has an unknown property '${
179 err.params.additionalProperty
180 }'. These properties are valid:\n${getSchemaPartText(err.parentSchema)}`;
181 if (!err.dataPath) {
182 switch (err.params.additionalProperty) {
183 case "debug":
184 return (
185 `${baseMessage}\n` +
186 "The 'debug' property was removed in webpack 2.0.0.\n" +
187 "Loaders should be updated to allow passing this option via loader options in module.rules.\n" +
188 "Until loaders are updated one can use the LoaderOptionsPlugin to switch loaders into debug mode:\n" +
189 "plugins: [\n" +
190 " new webpack.LoaderOptionsPlugin({\n" +
191 " debug: true\n" +
192 " })\n" +
193 "]"
194 );
195 }
196 return (
197 `${baseMessage}\n` +
198 "For typos: please correct them.\n" +
199 "For loader options: webpack >= v2.0.0 no longer allows custom properties in configuration.\n" +
200 " Loaders should be updated to allow passing options via loader options in module.rules.\n" +
201 " Until loaders are updated one can use the LoaderOptionsPlugin to pass these options to the loader:\n" +
202 " plugins: [\n" +
203 " new webpack.LoaderOptionsPlugin({\n" +
204 " // test: /\\.xxx$/, // may apply this only for some modules\n" +
205 " options: {\n" +
206 ` ${err.params.additionalProperty}: …\n` +
207 " }\n" +
208 " })\n" +
209 " ]"
210 );
211 }
212 return baseMessage;
213 } else if (err.keyword === "oneOf" || err.keyword === "anyOf") {
214 if (err.children && err.children.length > 0) {
215 if (err.schema.length === 1) {
216 const lastChild = err.children[err.children.length - 1];
217 const remainingChildren = err.children.slice(
218 0,
219 err.children.length - 1
220 );
221 return WebpackOptionsValidationError.formatValidationError(
222 Object.assign({}, lastChild, {
223 children: remainingChildren,
224 parentSchema: Object.assign(
225 {},
226 err.parentSchema,
227 lastChild.parentSchema
228 )
229 })
230 );
231 }
232 return (
233 `${dataPath} should be one of these:\n${getSchemaPartText(
234 err.parentSchema
235 )}\n` +
236 `Details:\n${filterChildren(err.children)
237 .map(
238 err =>
239 " * " +
240 indent(
241 WebpackOptionsValidationError.formatValidationError(err),
242 " ",
243 false
244 )
245 )
246 .join("\n")}`
247 );
248 }
249 return `${dataPath} should be one of these:\n${getSchemaPartText(
250 err.parentSchema
251 )}`;
252 } else if (err.keyword === "enum") {
253 if (
254 err.parentSchema &&
255 err.parentSchema.enum &&
256 err.parentSchema.enum.length === 1
257 ) {
258 return `${dataPath} should be ${getSchemaPartText(err.parentSchema)}`;
259 }
260 return `${dataPath} should be one of these:\n${getSchemaPartText(
261 err.parentSchema
262 )}`;
263 } else if (err.keyword === "allOf") {
264 return `${dataPath} should be:\n${getSchemaPartText(err.parentSchema)}`;
265 } else if (err.keyword === "type") {
266 switch (err.params.type) {
267 case "object":
268 return `${dataPath} should be an object.${getSchemaPartDescription(
269 err.parentSchema
270 )}`;
271 case "string":
272 return `${dataPath} should be a string.${getSchemaPartDescription(
273 err.parentSchema
274 )}`;
275 case "boolean":
276 return `${dataPath} should be a boolean.${getSchemaPartDescription(
277 err.parentSchema
278 )}`;
279 case "number":
280 return `${dataPath} should be a number.${getSchemaPartDescription(
281 err.parentSchema
282 )}`;
283 case "array":
284 return `${dataPath} should be an array:\n${getSchemaPartText(
285 err.parentSchema
286 )}`;
287 }
288 return `${dataPath} should be ${err.params.type}:\n${getSchemaPartText(
289 err.parentSchema
290 )}`;
291 } else if (err.keyword === "instanceof") {
292 return `${dataPath} should be an instance of ${getSchemaPartText(
293 err.parentSchema
294 )}`;
295 } else if (err.keyword === "required") {
296 const missingProperty = err.params.missingProperty.replace(/^\./, "");
297 return `${dataPath} misses the property '${missingProperty}'.\n${getSchemaPartText(
298 err.parentSchema,
299 ["properties", missingProperty]
300 )}`;
301 } else if (err.keyword === "minimum") {
302 return `${dataPath} ${err.message}.${getSchemaPartDescription(
303 err.parentSchema
304 )}`;
305 } else if (err.keyword === "uniqueItems") {
306 return `${dataPath} should not contain the item '${
307 err.data[err.params.i]
308 }' twice.${getSchemaPartDescription(err.parentSchema)}`;
309 } else if (
310 err.keyword === "minLength" ||
311 err.keyword === "minItems" ||
312 err.keyword === "minProperties"
313 ) {
314 if (err.params.limit === 1) {
315 return `${dataPath} should not be empty.${getSchemaPartDescription(
316 err.parentSchema
317 )}`;
318 } else {
319 return `${dataPath} ${err.message}${getSchemaPartDescription(
320 err.parentSchema
321 )}`;
322 }
323 } else if (err.keyword === "absolutePath") {
324 const baseMessage = `${dataPath}: ${
325 err.message
326 }${getSchemaPartDescription(err.parentSchema)}`;
327 if (dataPath === "configuration.output.filename") {
328 return (
329 `${baseMessage}\n` +
330 "Please use output.path to specify absolute path and output.filename for the file name."
331 );
332 }
333 return baseMessage;
334 } else {
335 return `${dataPath} ${err.message} (${JSON.stringify(
336 err,
337 null,
338 2
339 )}).\n${getSchemaPartText(err.parentSchema)}`;
340 }
341 }
342}
343
344module.exports = WebpackOptionsValidationError;