2016-09-09 18:52:34 +08:00
/ *
MIT License http : //www.opensource.org/licenses/mit-license.php
Author Gajus Kuizinas @ gajus
* /
2017-01-05 00:33:49 +08:00
"use strict" ;
2016-09-19 06:54:35 +08:00
2017-03-25 05:17:47 +08:00
const WebpackError = require ( "./WebpackError" ) ;
2017-01-05 00:33:49 +08:00
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 ;
2017-01-11 17:51:58 +08:00
} ;
2016-09-09 18:52:34 +08:00
2017-01-05 00:33:49 +08:00
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 ;
2017-01-11 17:51:58 +08:00
} ;
2017-01-05 00:33:49 +08:00
const indent = ( str , prefix , firstLine ) => {
if ( firstLine ) {
return prefix + str . replace ( /\n(?!$)/g , "\n" + prefix ) ;
} else {
return str . replace ( /\n(?!$)/g , ` \n ${ prefix } ` ) ;
}
2017-01-11 17:51:58 +08:00
} ;
2017-01-05 00:33:49 +08:00
2017-03-25 05:17:47 +08:00
class WebpackOptionsValidationError extends WebpackError {
2017-01-05 00:33:49 +08:00
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 ;
2017-02-11 06:35:11 +08:00
2017-02-17 00:16:47 +08:00
Error . captureStackTrace ( this , this . constructor ) ;
2017-01-05 00:33:49 +08:00
}
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 ) ) ;
2017-01-11 17:51:58 +08:00
} ;
2017-01-05 00:33:49 +08:00
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 ) ;
}
2016-09-19 06:54:35 +08:00
2017-01-05 00:33:49 +08:00
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 ) } ` ;
2016-09-21 02:18:52 +08:00
if ( ! err . dataPath ) {
2016-09-21 15:24:00 +08:00
switch ( err . params . additionalProperty ) {
case "debug" :
2017-01-05 00:33:49 +08:00
return ` ${ baseMessage } \n ` +
2016-09-21 15:24:00 +08:00
"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" +
2016-09-28 18:54:46 +08:00
"plugins: [\n" +
2016-09-21 15:24:00 +08:00
" new webpack.LoaderOptionsPlugin({\n" +
" debug: true\n" +
" })\n" +
2016-09-28 18:54:46 +08:00
"]" ;
2016-09-21 15:24:00 +08:00
}
2016-09-21 02:18:52 +08:00
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" +
2016-09-28 18:54:46 +08:00
" plugins: [\n" +
2016-09-21 02:18:52 +08:00
" new webpack.LoaderOptionsPlugin({\n" +
2016-09-21 17:26:46 +08:00
" // test: /\\.xxx$/, // may apply this only for some modules\n" +
2016-09-21 02:18:52 +08:00
" options: {\n" +
2017-01-05 00:33:49 +08:00
` ${ err . params . additionalProperty } : ... \n ` +
2016-09-21 02:18:52 +08:00
" }\n" +
" })\n" +
2016-09-28 18:54:46 +08:00
" ]" ;
2016-09-21 02:18:52 +08:00
}
return baseMessage ;
2017-01-05 00:33:49 +08:00
} else if ( err . keyword === "oneOf" || err . keyword === "anyOf" ) {
2016-12-14 18:34:31 +08:00
if ( err . children && err . children . length > 0 ) {
2017-01-05 00:33:49 +08:00
return ` ${ dataPath } should be one of these: \n ${ getSchemaPartText ( err . parentSchema ) } \n ` +
2017-01-11 17:51:58 +08:00
` Details: \n ${ err . children . map ( err => " * " + indent ( WebpackOptionsValidationError . formatValidationError ( err ) , " " , false ) ) . join ( "\n" ) } ` ;
2016-12-14 18:34:31 +08:00
}
2017-01-05 00:33:49 +08:00
return ` ${ dataPath } should be one of these: \n ${ getSchemaPartText ( err . parentSchema ) } ` ;
} else if ( err . keyword === "enum" ) {
2016-12-14 18:34:31 +08:00
if ( err . parentSchema && err . parentSchema . enum && err . parentSchema . enum . length === 1 ) {
2017-01-05 00:33:49 +08:00
return ` ${ dataPath } should be ${ getSchemaPartText ( err . parentSchema ) } ` ;
2016-12-14 18:34:31 +08:00
}
2017-01-05 00:33:49 +08:00
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" ) {
2016-09-19 06:54:35 +08:00
switch ( err . params . type ) {
case "object" :
2017-01-05 00:33:49 +08:00
return ` ${ dataPath } should be an object. ` ;
2016-09-19 06:54:35 +08:00
case "string" :
2017-01-05 00:33:49 +08:00
return ` ${ dataPath } should be a string. ` ;
2016-09-19 06:54:35 +08:00
case "boolean" :
2017-01-05 00:33:49 +08:00
return ` ${ dataPath } should be a boolean. ` ;
2016-09-19 06:54:35 +08:00
case "number" :
2017-01-05 00:33:49 +08:00
return ` ${ dataPath } should be a number. ` ;
2016-12-14 18:34:31 +08:00
case "array" :
2017-01-05 00:33:49 +08:00
return ` ${ dataPath } should be an array: \n ${ getSchemaPartText ( err . parentSchema ) } ` ;
2016-09-19 06:54:35 +08:00
}
2017-01-05 00:33:49 +08:00
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 === "minLength" || err . keyword === "minItems" ) {
2016-09-19 06:54:35 +08:00
if ( err . params . limit === 1 )
2017-01-05 00:33:49 +08:00
return ` ${ dataPath } should not be empty. ` ;
2016-09-19 06:54:35 +08:00
else
2017-01-05 00:33:49 +08:00
return ` ${ dataPath } ${ err . message } ` ;
2017-02-08 19:54:55 +08:00
} else if ( err . keyword === "absolutePath" ) {
2017-03-25 05:21:30 +08:00
const baseMessage = ` ${ dataPath } : ${ err . message } ` ;
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 ;
2017-01-05 00:33:49 +08:00
} else {
// eslint-disable-line no-fallthrough
return ` ${ dataPath } ${ err . message } ( ${ JSON . stringify ( err , 0 , 2 ) } ). \n ${ getSchemaPartText ( err . parentSchema ) } ` ;
2016-09-19 06:54:35 +08:00
}
}
}
2017-01-05 00:33:49 +08:00
module . exports = WebpackOptionsValidationError ;