Skip to content

Commit 8c3d97d

Browse files
committed
add test
better error messages validate array more options in schema stricter schema fix some old configs
1 parent 9e0a95e commit 8c3d97d

File tree

12 files changed

+1123
-557
lines changed

12 files changed

+1123
-557
lines changed

examples/coffee-script/webpack.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@ module.exports = {
55
]
66
},
77
resolve: {
8-
extensions: ["", ".web.coffee", ".web.js", ".coffee", ".js"]
8+
extensions: [".web.coffee", ".web.js", ".coffee", ".js"]
99
}
1010
}

lib/WebpackOptionsValidationError.js

Lines changed: 144 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,157 @@
22
MIT License http://www.opensource.org/licenses/mit-license.php
33
Author Gajus Kuizinas @gajus
44
*/
5+
var webpackOptionsSchema = require("../schemas/webpackOptionsSchema.json");
6+
57
function WebpackOptionsValidationError(validationErrors) {
68
Error.call(this);
79
Error.captureStackTrace(this, WebpackOptionsValidationError);
810
this.name = "WebpackOptionsValidationError";
9-
this.message = "Invalid configuration object. Webpack has been initialised using a configuration object that does not match the API schema. Inspect validationErrors property of the error to learn what checks have failed.";
11+
this.message = "Invalid configuration object. " +
12+
"Webpack has been initialised using a configuration object that does not match the API schema.\n" +
13+
validationErrors.map(function(err) {
14+
return " - " + indent(WebpackOptionsValidationError.formatValidationError(err), " ", false);
15+
}).join("\n");
1016
this.validationErrors = validationErrors;
1117
}
1218
module.exports = WebpackOptionsValidationError;
1319

1420
WebpackOptionsValidationError.prototype = Object.create(Error.prototype);
1521
WebpackOptionsValidationError.prototype.constructor = WebpackOptionsValidationError;
22+
23+
WebpackOptionsValidationError.formatValidationError = function formatValidationError(err) {
24+
var dataPath = "configuration" + err.dataPath;
25+
switch(err.keyword) {
26+
case "additionalProperties":
27+
return dataPath + " has an unknown property '" + err.params.additionalProperty + "'. These properties are valid:\n" +
28+
getSchemaPartText(err.parentSchema);
29+
case "oneOf":
30+
case "anyOf":
31+
case "enum":
32+
return dataPath + " should be one of these:\n" +
33+
getSchemaPartText(err.parentSchema);
34+
case "allOf":
35+
return dataPath + " should be:\n" +
36+
getSchemaPartText(err.parentSchema);
37+
case "type":
38+
switch(err.params.type) {
39+
case "object":
40+
return dataPath + " should be an object.";
41+
case "string":
42+
return dataPath + " should be a string.";
43+
case "boolean":
44+
return dataPath + " should be a boolean.";
45+
case "number":
46+
return dataPath + " should be a number.";
47+
}
48+
return dataPath + " should be " + err.params.type + ":\n" +
49+
getSchemaPartText(err.parentSchema);
50+
case "required":
51+
var missingProperty = err.params.missingProperty.replace(/^\./, "");
52+
return dataPath + " misses the property '" + missingProperty + "'.\n" +
53+
getSchemaPartText(err.parentSchema, ["properties", missingProperty]);
54+
case "minLength":
55+
if(err.params.limit === 1)
56+
return dataPath + " should not be empty.";
57+
else
58+
return dataPath + " " + err.message;
59+
default:
60+
return dataPath + " " + err.message + " (" + JSON.stringify(err, 0, 2) + ").\n" +
61+
getSchemaPartText(err.parentSchema);
62+
}
63+
}
64+
65+
function getSchemaPart(path, parents, additionalPath) {
66+
parents = parents || 0;
67+
path = path.split("/");
68+
path = path.slice(0, path.length - parents);
69+
if(additionalPath) {
70+
additionalPath = additionalPath.split("/");
71+
path = path.concat(additionalPath);
72+
}
73+
var schemaPart = webpackOptionsSchema;
74+
for(var i = 1; i < path.length; i++) {
75+
var inner = schemaPart[path[i]];
76+
if(inner)
77+
schemaPart = inner;
78+
}
79+
return schemaPart;
80+
}
81+
82+
function getSchemaPartText2(path, parents, additionalPath) {
83+
var schemaPart = getSchemaPart(path, parents, additionalPath);
84+
while(schemaPart.$ref) schemaPart = getSchemaPart(schemaPart.$ref);
85+
var schemaText = WebpackOptionsValidationError.formatSchema(schemaPart);
86+
if(schemaPart.description)
87+
schemaText += "\n" + schemaPart.description;
88+
return schemaText;
89+
}
90+
91+
function getSchemaPartText(schemaPart, additionalPath) {
92+
if(additionalPath) {
93+
for(var i = 0; i < additionalPath.length; i++) {
94+
var inner = schemaPart[additionalPath[i]];
95+
if(inner)
96+
schemaPart = inner;
97+
}
98+
}
99+
while(schemaPart.$ref) schemaPart = getSchemaPart(schemaPart.$ref);
100+
var schemaText = WebpackOptionsValidationError.formatSchema(schemaPart);
101+
if(schemaPart.description)
102+
schemaText += "\n" + schemaPart.description;
103+
return schemaText;
104+
}
105+
106+
function formatSchema(schema, prevSchemas) {
107+
prevSchemas = prevSchemas || [];
108+
109+
function formatInnerSchema(innerSchema, addSelf) {
110+
if(!addSelf) return formatSchema(innerSchema, prevSchemas);
111+
if(prevSchemas.indexOf(innerSchema) >= 0) return "(recursive)";
112+
return formatSchema(innerSchema, prevSchemas.concat(schema));
113+
}
114+
switch(schema.type) {
115+
case "string":
116+
return "string";
117+
case "boolean":
118+
return "boolean";
119+
case "object":
120+
if(schema.properties) {
121+
var required = schema.required || [];
122+
return "object { " + Object.keys(schema.properties).map(function(property) {
123+
if(required.indexOf(property) < 0) return property + "?";
124+
return property;
125+
}).join(", ") + " }";
126+
}
127+
if(schema.additionalProperties) {
128+
return "object { <key>: " + formatInnerSchema(schema.additionalProperties) + " }";
129+
}
130+
return "object";
131+
case "array":
132+
return "[" + formatInnerSchema(schema.items) + "]";
133+
}
134+
switch(schema.instanceof) {
135+
case "Function":
136+
return "function";
137+
case "RegExp":
138+
return "RegExp";
139+
}
140+
if(schema.$ref) return formatInnerSchema(getSchemaPart(schema.$ref), true);
141+
if(schema.allOf) return schema.allOf.map(formatInnerSchema).join(" & ");
142+
if(schema.oneOf) return schema.oneOf.map(formatInnerSchema).join(" | ");
143+
if(schema.anyOf) return schema.anyOf.map(formatInnerSchema).join(" | ");
144+
if(schema.enum) return schema.enum.map(function(item) {
145+
return JSON.stringify(item);
146+
}).join(" | ");
147+
return JSON.stringify(schema, 0, 2);
148+
}
149+
150+
function indent(str, prefix, firstLine) {
151+
if(firstLine) {
152+
return prefix + str.replace(/\n(?!$)/g, "\n" + prefix);
153+
} else {
154+
return str.replace(/\n(?!$)/g, "\n" + prefix);
155+
}
156+
}
157+
158+
WebpackOptionsValidationError.formatSchema = formatSchema;

lib/validateWebpackOptions.js

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,62 @@
22
MIT License http://www.opensource.org/licenses/mit-license.php
33
Author Gajus Kuizinas @gajus
44
*/
5-
var webpackOptionsSchema = require("./../schemas/webpackOptionsSchema.json");
5+
var webpackOptionsSchema = require("../schemas/webpackOptionsSchema.json");
66
var Ajv = require("ajv");
7-
var ajv = new Ajv();
7+
var ajv = new Ajv({
8+
errorDataPath: "configuration",
9+
allErrors: true,
10+
verbose: true
11+
});
812
var validate = ajv.compile(webpackOptionsSchema);
13+
914
function validateWebpackOptions(options) {
15+
if(Array.isArray(options)) {
16+
var errors = options.map(validateObject);
17+
errors.forEach(function(list, idx) {
18+
list.forEach(function applyPrefix(err) {
19+
err.dataPath = "[" + idx + "]" + err.dataPath;
20+
if(err.children) {
21+
err.children.forEach(applyPrefix);
22+
}
23+
});
24+
});
25+
return errors.reduce(function(arr, items) {
26+
return arr.concat(items);
27+
}, []);
28+
} else {
29+
return validateObject(options);
30+
}
31+
}
32+
33+
function validateObject(options) {
1034
var valid = validate(options);
11-
return valid ? [] : validate.errors;
35+
return valid ? [] : filterErrors(validate.errors);
36+
}
37+
38+
function filterErrors(errors) {
39+
var errorsByDataPath = {};
40+
var newErrors = [];
41+
errors.forEach(function(err) {
42+
var dataPath = err.dataPath;
43+
var key = "$" + dataPath;
44+
if(errorsByDataPath[key]) {
45+
var oldError = errorsByDataPath[key];
46+
var idx = newErrors.indexOf(oldError);
47+
newErrors.splice(idx, 1);
48+
if(oldError.children) {
49+
var children = oldError.children;
50+
delete oldError.children;
51+
children.push(oldError);
52+
err.children = children;
53+
} else {
54+
err.children = [oldError];
55+
}
56+
}
57+
errorsByDataPath[key] = err;
58+
newErrors.push(err);
59+
});
60+
return newErrors;
1261
}
62+
1363
module.exports = validateWebpackOptions;

lib/webpack.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,16 @@ var validateWebpackOptions = require("./validateWebpackOptions");
1111
var WebpackOptionsValidationError = require("./WebpackOptionsValidationError");
1212

1313
function webpack(options, callback) {
14+
var webpackOptionsValidationErrors = validateWebpackOptions(options);
15+
if(webpackOptionsValidationErrors.length) {
16+
throw new WebpackOptionsValidationError(webpackOptionsValidationErrors);
17+
}
1418
var compiler;
1519
if(Array.isArray(options)) {
1620
compiler = new MultiCompiler(options.map(function(options) {
1721
return webpack(options);
1822
}));
1923
} else if(typeof options === "object") {
20-
var webpackOptionsValidationErrors = validateWebpackOptions(options);
21-
if(webpackOptionsValidationErrors.length) {
22-
throw new WebpackOptionsValidationError(webpackOptionsValidationErrors);
23-
}
2424
new WebpackOptionsDefaulter().process(options);
2525

2626
compiler = new Compiler();
@@ -52,6 +52,7 @@ webpack.WebpackOptionsApply = WebpackOptionsApply;
5252
webpack.Compiler = Compiler;
5353
webpack.MultiCompiler = MultiCompiler;
5454
webpack.NodeEnvironmentPlugin = NodeEnvironmentPlugin;
55+
webpack.validate = validateWebpackOptions;
5556

5657
function exportPlugins(exports, path, plugins) {
5758
plugins.forEach(function(name) {

0 commit comments

Comments
 (0)