WebpackOptionsValidationError.js
8.98 KB
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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Gajus Kuizinas @gajus
*/
"use strict";
const WebpackError = require("./WebpackError");
const webpackOptionsSchema = require("../schemas/webpackOptionsSchema.json");
const getSchemaPart = (path, parents, additionalPath) => {
parents = parents || 0;
path = path.split("/");
path = path.slice(0, path.length - parents);
if(additionalPath) {
additionalPath = additionalPath.split("/");
path = path.concat(additionalPath);
}
let schemaPart = webpackOptionsSchema;
for(let i = 1; i < path.length; i++) {
const inner = schemaPart[path[i]];
if(inner)
schemaPart = inner;
}
return schemaPart;
};
const getSchemaPartText = (schemaPart, additionalPath) => {
if(additionalPath) {
for(let i = 0; i < additionalPath.length; i++) {
const inner = schemaPart[additionalPath[i]];
if(inner)
schemaPart = inner;
}
}
while(schemaPart.$ref) schemaPart = getSchemaPart(schemaPart.$ref);
let schemaText = WebpackOptionsValidationError.formatSchema(schemaPart);
if(schemaPart.description)
schemaText += `\n-> ${schemaPart.description}`;
return schemaText;
};
const getSchemaPartDescription = schemaPart => {
while(schemaPart.$ref) schemaPart = getSchemaPart(schemaPart.$ref);
if(schemaPart.description)
return `\n-> ${schemaPart.description}`;
return "";
};
const filterChildren = children => {
return children.filter(err => err.keyword !== "anyOf" && err.keyword !== "allOf" && err.keyword !== "oneOf");
};
const indent = (str, prefix, firstLine) => {
if(firstLine) {
return prefix + str.replace(/\n(?!$)/g, "\n" + prefix);
} else {
return str.replace(/\n(?!$)/g, `\n${prefix}`);
}
};
class WebpackOptionsValidationError extends WebpackError {
constructor(validationErrors) {
super();
this.name = "WebpackOptionsValidationError";
this.message = "Invalid configuration object. " +
"Webpack has been initialised using a configuration object that does not match the API schema.\n" +
validationErrors.map(err => " - " + indent(WebpackOptionsValidationError.formatValidationError(err), " ", false)).join("\n");
this.validationErrors = validationErrors;
Error.captureStackTrace(this, this.constructor);
}
static formatSchema(schema, prevSchemas) {
prevSchemas = prevSchemas || [];
const formatInnerSchema = (innerSchema, addSelf) => {
if(!addSelf) return WebpackOptionsValidationError.formatSchema(innerSchema, prevSchemas);
if(prevSchemas.indexOf(innerSchema) >= 0) return "(recursive)";
return WebpackOptionsValidationError.formatSchema(innerSchema, prevSchemas.concat(schema));
};
if(schema.type === "string") {
if(schema.minLength === 1)
return "non-empty string";
else if(schema.minLength > 1)
return `string (min length ${schema.minLength})`;
return "string";
} else if(schema.type === "boolean") {
return "boolean";
} else if(schema.type === "number") {
return "number";
} else if(schema.type === "object") {
if(schema.properties) {
const required = schema.required || [];
return `object { ${Object.keys(schema.properties).map(property => {
if(required.indexOf(property) < 0) return property + "?";
return property;
}).concat(schema.additionalProperties ? ["..."] : []).join(", ")} }`;
}
if(schema.additionalProperties) {
return `object { <key>: ${formatInnerSchema(schema.additionalProperties)} }`;
}
return "object";
} else if(schema.type === "array") {
return `[${formatInnerSchema(schema.items)}]`;
}
switch(schema.instanceof) {
case "Function":
return "function";
case "RegExp":
return "RegExp";
}
if(schema.$ref) return formatInnerSchema(getSchemaPart(schema.$ref), true);
if(schema.allOf) return schema.allOf.map(formatInnerSchema).join(" & ");
if(schema.oneOf) return schema.oneOf.map(formatInnerSchema).join(" | ");
if(schema.anyOf) return schema.anyOf.map(formatInnerSchema).join(" | ");
if(schema.enum) return schema.enum.map(item => JSON.stringify(item)).join(" | ");
return JSON.stringify(schema, 0, 2);
}
static formatValidationError(err) {
const dataPath = `configuration${err.dataPath}`;
if(err.keyword === "additionalProperties") {
const baseMessage = `${dataPath} has an unknown property '${err.params.additionalProperty}'. These properties are valid:\n${getSchemaPartText(err.parentSchema)}`;
if(!err.dataPath) {
switch(err.params.additionalProperty) {
case "debug":
return `${baseMessage}\n` +
"The 'debug' property was removed in webpack 2.\n" +
"Loaders should be updated to allow passing this option via loader options in module.rules.\n" +
"Until loaders are updated one can use the LoaderOptionsPlugin to switch loaders into debug mode:\n" +
"plugins: [\n" +
" new webpack.LoaderOptionsPlugin({\n" +
" debug: true\n" +
" })\n" +
"]";
}
return baseMessage + "\n" +
"For typos: please correct them.\n" +
"For loader options: webpack 2 no longer allows custom properties in configuration.\n" +
" Loaders should be updated to allow passing options via loader options in module.rules.\n" +
" Until loaders are updated one can use the LoaderOptionsPlugin to pass these options to the loader:\n" +
" plugins: [\n" +
" new webpack.LoaderOptionsPlugin({\n" +
" // test: /\\.xxx$/, // may apply this only for some modules\n" +
" options: {\n" +
` ${err.params.additionalProperty}: ...\n` +
" }\n" +
" })\n" +
" ]";
}
return baseMessage;
} else if(err.keyword === "oneOf" || err.keyword === "anyOf") {
if(err.children && err.children.length > 0) {
if(err.schema.length === 1) {
const lastChild = err.children[err.children.length - 1];
const remainingChildren = err.children.slice(0, err.children.length - 1);
return WebpackOptionsValidationError.formatValidationError(Object.assign({}, lastChild, {
children: remainingChildren,
parentSchema: Object.assign({}, err.parentSchema, lastChild.parentSchema)
}));
}
return `${dataPath} should be one of these:\n${getSchemaPartText(err.parentSchema)}\n` +
`Details:\n${filterChildren(err.children).map(err => " * " + indent(WebpackOptionsValidationError.formatValidationError(err), " ", false)).join("\n")}`;
}
return `${dataPath} should be one of these:\n${getSchemaPartText(err.parentSchema)}`;
} else if(err.keyword === "enum") {
if(err.parentSchema && err.parentSchema.enum && err.parentSchema.enum.length === 1) {
return `${dataPath} should be ${getSchemaPartText(err.parentSchema)}`;
}
return `${dataPath} should be one of these:\n${getSchemaPartText(err.parentSchema)}`;
} else if(err.keyword === "allOf") {
return `${dataPath} should be:\n${getSchemaPartText(err.parentSchema)}`;
} else if(err.keyword === "type") {
switch(err.params.type) {
case "object":
return `${dataPath} should be an object.${getSchemaPartDescription(err.parentSchema)}`;
case "string":
return `${dataPath} should be a string.${getSchemaPartDescription(err.parentSchema)}`;
case "boolean":
return `${dataPath} should be a boolean.${getSchemaPartDescription(err.parentSchema)}`;
case "number":
return `${dataPath} should be a number.${getSchemaPartDescription(err.parentSchema)}`;
case "array":
return `${dataPath} should be an array:\n${getSchemaPartText(err.parentSchema)}`;
}
return `${dataPath} should be ${err.params.type}:\n${getSchemaPartText(err.parentSchema)}`;
} else if(err.keyword === "instanceof") {
return `${dataPath} should be an instance of ${getSchemaPartText(err.parentSchema)}`;
} else if(err.keyword === "required") {
const missingProperty = err.params.missingProperty.replace(/^\./, "");
return `${dataPath} misses the property '${missingProperty}'.\n${getSchemaPartText(err.parentSchema, ["properties", missingProperty])}`;
} else if(err.keyword === "minimum") {
return `${dataPath} ${err.message}.${getSchemaPartDescription(err.parentSchema)}`;
} else if(err.keyword === "uniqueItems") {
return `${dataPath} should not contain the item '${err.data[err.params.i]}' twice.${getSchemaPartDescription(err.parentSchema)}`;
} else if(err.keyword === "minLength" || err.keyword === "minItems" || err.keyword === "minProperties") {
if(err.params.limit === 1)
return `${dataPath} should not be empty.${getSchemaPartDescription(err.parentSchema)}`;
else
return `${dataPath} ${err.message}${getSchemaPartDescription(err.parentSchema)}`;
} else if(err.keyword === "absolutePath") {
const baseMessage = `${dataPath}: ${err.message}${getSchemaPartDescription(err.parentSchema)}`;
if(dataPath === "configuration.output.filename") {
return `${baseMessage}\n` +
"Please use output.path to specify absolute path and output.filename for the file name.";
}
return baseMessage;
} else {
// eslint-disable-line no-fallthrough
return `${dataPath} ${err.message} (${JSON.stringify(err, 0, 2)}).\n${getSchemaPartText(err.parentSchema)}`;
}
}
}
module.exports = WebpackOptionsValidationError;