123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390 |
- /*
- MIT License http://www.opensource.org/licenses/mit-license.php
- Author Gajus Kuizinas @gajus
- */
- "use strict";
- const WebpackError = require("./WebpackError");
- const webpackOptionsSchema = require("../schemas/WebpackOptions.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 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 => {
- 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) => {
- 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(
- "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.name = "WebpackOptionsValidationError";
- 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.includes(innerSchema)) {
- return "(recursive)";
- }
- return WebpackOptionsValidationError.formatSchema(
- innerSchema,
- prevSchemas.concat(schema)
- );
- };
- if (schema.type === "string") {
- if (schema.minLength === 1) {
- return "non-empty string";
- }
- if (schema.minLength > 1) {
- return `string (min length ${schema.minLength})`;
- }
- return "string";
- }
- if (schema.type === "boolean") {
- return "boolean";
- }
- if (schema.type === "number") {
- return "number";
- }
- if (schema.type === "object") {
- if (schema.properties) {
- const required = schema.required || [];
- return `object { ${Object.keys(schema.properties)
- .map(property => {
- if (!required.includes(property)) return property + "?";
- return property;
- })
- .concat(schema.additionalProperties ? ["…"] : [])
- .join(", ")} }`;
- }
- if (schema.additionalProperties) {
- return `object { <key>: ${formatInnerSchema(
- schema.additionalProperties
- )} }`;
- }
- return "object";
- }
- if (schema.type === "array") {
- return `[${formatInnerSchema(schema.items)}]`;
- }
- switch (schema.instanceof) {
- case "Function":
- return "function";
- case "RegExp":
- return "RegExp";
- }
- if (schema.enum) {
- return schema.enum.map(item => JSON.stringify(item)).join(" | ");
- }
- 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(" | ");
- }
- return JSON.stringify(schema, null, 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.0.0.\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 >= v2.0.0 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
- )
- })
- );
- }
- 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${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) {
- 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 {
- return `${dataPath} ${err.message}${getSchemaPartDescription(
- err.parentSchema
- )}`;
- }
- } else if (err.keyword === "not") {
- return `${dataPath} should not be ${getSchemaPartText(
- err.schema
- )}\n${getSchemaPartText(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 {
- return `${dataPath} ${err.message} (${JSON.stringify(
- err,
- null,
- 2
- )}).\n${getSchemaPartText(err.parentSchema)}`;
- }
- }
- }
- module.exports = WebpackOptionsValidationError;
|