Skip to content

Commit

Permalink
improve validation errors
Browse files Browse the repository at this point in the history
  • Loading branch information
sokra committed May 22, 2019
1 parent 0963d74 commit a4406ff
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 93 deletions.
2 changes: 2 additions & 0 deletions declarations/WebpackOptions.d.ts
Expand Up @@ -22,6 +22,8 @@ export type EntryDynamic = () => EntryStatic | Promise<EntryStatic>;
*/
export type EntryStatic = EntryObject | EntryItem;
/**
* A non-empty array of non-empty strings
*
* This interface was referenced by `WebpackOptions`'s JSON-Schema
* via the `definition` "NonEmptyArrayOfUniqueStringValues".
*/
Expand Down
56 changes: 48 additions & 8 deletions lib/WebpackOptionsValidationError.js
Expand Up @@ -50,13 +50,34 @@ const getSchemaPartDescription = schemaPart => {
return "";
};

const SPECIFICITY = {
type: 1,
oneOf: 1,
anyOf: 1,
allOf: 1,
additionalProperties: 2,
enum: 1,
instanceof: 1,
required: 2,
minimum: 2,
uniqueItems: 2,
minLength: 2,
minItems: 2,
minProperties: 2,
absolutePath: 2
};

const filterMax = (array, fn) => {
const max = array.reduce((max, item) => Math.max(max, fn(item)), 0);
return array.filter(item => fn(item) === max);
};

const filterChildren = children => {
return children.filter(
err =>
err.keyword !== "anyOf" &&
err.keyword !== "allOf" &&
err.keyword !== "oneOf"
children = filterMax(children, err =>
err.dataPath ? err.dataPath.length : 0
);
children = filterMax(children, err => SPECIFICITY[err.keyword] || 2);
return children;
};

const indent = (str, prefix, firstLine) => {
Expand Down Expand Up @@ -230,11 +251,17 @@ class WebpackOptionsValidationError extends WebpackError {
})
);
}
const children = filterChildren(err.children);
if (children.length === 1) {
return WebpackOptionsValidationError.formatValidationError(
children[0]
);
}
return (
`${dataPath} should be one of these:\n${getSchemaPartText(
err.parentSchema
)}\n` +
`Details:\n${filterChildren(err.children)
`Details:\n${children
.map(
err =>
" * " +
Expand All @@ -251,7 +278,6 @@ class WebpackOptionsValidationError extends WebpackError {
err.parentSchema
)}`;
} else if (err.keyword === "enum") {
console.log(err.parentSchema);
if (
err.parentSchema &&
err.parentSchema.enum &&
Expand Down Expand Up @@ -314,7 +340,21 @@ class WebpackOptionsValidationError extends WebpackError {
err.keyword === "minProperties"
) {
if (err.params.limit === 1) {
return `${dataPath} should not be empty.${getSchemaPartDescription(
switch (err.keyword) {
case "minLength":
return `${dataPath} should be an non-empty string.${getSchemaPartDescription(
err.parentSchema
)}`;
case "minItems":
return `${dataPath} should be an non-empty array.${getSchemaPartDescription(
err.parentSchema
)}`;
case "minProperties":
return `${dataPath} should be an non-empty object.${getSchemaPartDescription(
err.parentSchema
)}`;
}
return `${dataPath} should be not empty.${getSchemaPartDescription(
err.parentSchema
)}`;
} else {
Expand Down
1 change: 1 addition & 0 deletions schemas/WebpackOptions.json
Expand Up @@ -385,6 +385,7 @@
}
},
"NonEmptyArrayOfUniqueStringValues": {
"description": "A non-empty array of non-empty strings",
"type": "array",
"items": {
"description": "A non-empty string",
Expand Down
102 changes: 17 additions & 85 deletions test/Validation.test.js
Expand Up @@ -43,18 +43,8 @@ describe("Validation", () => {
msg =>
expect(msg).toMatchInlineSnapshot(`
"Invalid configuration object. Webpack has been initialised using a configuration object that does not match the API schema.
- configuration.entry should be one of these:
function | object { <key>: non-empty string | [non-empty string] } | non-empty string | [non-empty string]
-> The entry point(s) of the compilation.
Details:
* configuration.entry should be an instance of function
-> A Function returning an entry object, an entry string, an entry array or a promise to these things.
* configuration.entry should be an object.
-> Multiple entry bundles are created. The key is the chunk name. The value can be a string or an array.
* configuration.entry should not be empty.
-> An entry point without name. The string is resolved to a module which is loaded upon startup.
* configuration.entry should be an array:
[non-empty string]"
- configuration.entry should be an non-empty string.
-> An entry point without name. The string is resolved to a module which is loaded upon startup."
`)
);

Expand All @@ -68,19 +58,8 @@ describe("Validation", () => {
msg =>
expect(msg).toMatchInlineSnapshot(`
"Invalid configuration object. Webpack has been initialised using a configuration object that does not match the API schema.
- configuration.entry should be one of these:
function | object { <key>: non-empty string | [non-empty string] } | non-empty string | [non-empty string]
-> The entry point(s) of the compilation.
Details:
* configuration.entry should be an instance of function
-> A Function returning an entry object, an entry string, an entry array or a promise to these things.
* configuration.entry['bundle'] should be a string.
-> The string is resolved to a module which is loaded upon startup.
* configuration.entry['bundle'] should not be empty.
* configuration.entry should be a string.
-> An entry point without name. The string is resolved to a module which is loaded upon startup.
* configuration.entry should be an array:
[non-empty string]"
- configuration.entry['bundle'] should be an non-empty array.
-> A non-empty array of non-empty strings"
`)
);

Expand Down Expand Up @@ -122,17 +101,8 @@ describe("Validation", () => {
msg =>
expect(msg).toMatchInlineSnapshot(`
"Invalid configuration object. Webpack has been initialised using a configuration object that does not match the API schema.
- configuration.entry should be one of these:
function | object { <key>: non-empty string | [non-empty string] } | non-empty string | [non-empty string]
-> The entry point(s) of the compilation.
Details:
* configuration.entry should be an instance of function
-> A Function returning an entry object, an entry string, an entry array or a promise to these things.
* configuration.entry should be an object.
-> Multiple entry bundles are created. The key is the chunk name. The value can be a string or an array.
* configuration.entry should be a string.
-> An entry point without name. The string is resolved to a module which is loaded upon startup.
* configuration.entry should not contain the item 'abc' twice."
- configuration.entry should not contain the item 'abc' twice.
-> A non-empty array of non-empty strings"
`)
);

Expand All @@ -147,18 +117,8 @@ describe("Validation", () => {
msg =>
expect(msg).toMatchInlineSnapshot(`
"Invalid configuration object. Webpack has been initialised using a configuration object that does not match the API schema.
- configuration.entry should be one of these:
function | object { <key>: non-empty string | [non-empty string] } | non-empty string | [non-empty string]
-> The entry point(s) of the compilation.
Details:
* configuration.entry should be an instance of function
-> A Function returning an entry object, an entry string, an entry array or a promise to these things.
* configuration.entry should be an object.
-> Multiple entry bundles are created. The key is the chunk name. The value can be a string or an array.
* configuration.entry should be a string.
-> An entry point without name. The string is resolved to a module which is loaded upon startup.
* configuration.entry[0] should be a string.
-> A non-empty string
- configuration.entry[0] should be a string.
-> A non-empty string
- configuration.output.filename should be one of these:
string | function
-> Specifies the name of each output file on disk. You must **not** specify an absolute path here! The \`output.path\` option determines the location on disk the files are written to, filename is used solely for naming the individual files.
Expand All @@ -184,18 +144,8 @@ describe("Validation", () => {
msg =>
expect(msg).toMatchInlineSnapshot(`
"Invalid configuration object. Webpack has been initialised using a configuration object that does not match the API schema.
- configuration[0].entry should be one of these:
function | object { <key>: non-empty string | [non-empty string] } | non-empty string | [non-empty string]
-> The entry point(s) of the compilation.
Details:
* configuration[0].entry should be an instance of function
-> A Function returning an entry object, an entry string, an entry array or a promise to these things.
* configuration[0].entry should be an object.
-> Multiple entry bundles are created. The key is the chunk name. The value can be a string or an array.
* configuration[0].entry should be a string.
-> An entry point without name. The string is resolved to a module which is loaded upon startup.
* configuration[0].entry[0] should be a string.
-> A non-empty string
- configuration[0].entry[0] should be a string.
-> A non-empty string
- configuration[1].output.filename should be one of these:
string | function
-> Specifies the name of each output file on disk. You must **not** specify an absolute path here! The \`output.path\` option determines the location on disk the files are written to, filename is used solely for naming the individual files.
Expand Down Expand Up @@ -305,13 +255,8 @@ describe("Validation", () => {
msg =>
expect(msg).toMatchInlineSnapshot(`
"Invalid configuration object. Webpack has been initialised using a configuration object that does not match the API schema.
- configuration.output.filename should be one of these:
string | function
-> Specifies the name of each output file on disk. You must **not** specify an absolute path here! The \`output.path\` option determines the location on disk the files are written to, filename is used solely for naming the individual files.
Details:
* configuration.output.filename: A relative path is expected. However, the provided value \\"/bar\\" is an absolute path!
Please use output.path to specify absolute path and output.filename for the file name.
* configuration.output.filename should be an instance of function"
- configuration.output.filename: A relative path is expected. However, the provided value \\"/bar\\" is an absolute path!
Please use output.path to specify absolute path and output.filename for the file name."
`)
);

Expand Down Expand Up @@ -347,15 +292,8 @@ describe("Validation", () => {
.replace(/"none" \| .+/g, '"none" | ...')
).toMatchInlineSnapshot(`
"Invalid configuration object. Webpack has been initialised using a configuration object that does not match the API schema.
- configuration.stats should be one of these:
object {...} | boolean | \\"none\\" | ...
-> Used by the webpack CLI program to pass stats options.
Details:
* configuration.stats has an unknown property 'foobar'. These properties are valid:
object {...}
* configuration.stats should be a boolean.
* configuration.stats should be one of these:
\\"none\\" | ..."
- configuration.stats has an unknown property 'foobar'. These properties are valid:
object {...}"
`);
}
);
Expand Down Expand Up @@ -449,15 +387,9 @@ describe("Validation", () => {
msg =>
expect(msg).toMatchInlineSnapshot(`
"Invalid configuration object. Webpack has been initialised using a configuration object that does not match the API schema.
- configuration.plugins[0] should be one of these:
object { apply, … } | function
-> Plugin of type object or instanceof Function
Details:
* configuration.plugins[0] misses the property 'apply'.
function
-> The run point of the plugin, required method.
* configuration.plugins[0] should be an instance of function
-> Function acting as plugin"
- configuration.plugins[0] misses the property 'apply'.
function
-> The run point of the plugin, required method."
`)
);

Expand Down

0 comments on commit a4406ff

Please sign in to comment.