diff --git a/declarations/WebpackOptions.d.ts b/declarations/WebpackOptions.d.ts index d6b2c9996..7e45ddbd4 100644 --- a/declarations/WebpackOptions.d.ts +++ b/declarations/WebpackOptions.d.ts @@ -22,6 +22,8 @@ export type EntryDynamic = () => EntryStatic | Promise; */ 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". */ diff --git a/lib/WebpackOptionsValidationError.js b/lib/WebpackOptionsValidationError.js index 62f53f81f..b3be3012d 100644 --- a/lib/WebpackOptionsValidationError.js +++ b/lib/WebpackOptionsValidationError.js @@ -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) => { @@ -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 => " * " + @@ -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 && @@ -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 { diff --git a/schemas/WebpackOptions.json b/schemas/WebpackOptions.json index 905c6e804..53e0948f4 100644 --- a/schemas/WebpackOptions.json +++ b/schemas/WebpackOptions.json @@ -385,6 +385,7 @@ } }, "NonEmptyArrayOfUniqueStringValues": { + "description": "A non-empty array of non-empty strings", "type": "array", "items": { "description": "A non-empty string", diff --git a/test/Validation.test.js b/test/Validation.test.js index 47314e32a..e82f8de54 100644 --- a/test/Validation.test.js +++ b/test/Validation.test.js @@ -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 { : 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." `) ); @@ -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 { : 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" `) ); @@ -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 { : 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" `) ); @@ -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 { : 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. @@ -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 { : 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. @@ -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." `) ); @@ -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 {...}" `); } ); @@ -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." `) );