diff --git a/packages/babel-core/src/config/config-descriptors.js b/packages/babel-core/src/config/config-descriptors.js index 52dd2b6df039..d9218a87c020 100644 --- a/packages/babel-core/src/config/config-descriptors.js +++ b/packages/babel-core/src/config/config-descriptors.js @@ -15,7 +15,6 @@ import type { PluginList, PluginItem, } from "./validation/options"; -import { assertNoUnwrappedItemOptionPairs } from "./validation/options"; // Represents a config object and functions to lazily load the descriptors // for the plugins and presets so we don't load the plugins/presets unless @@ -230,7 +229,6 @@ function createDescriptors( alias: string, ownPass?: boolean, ): Array { - assertNoUnwrappedItemOptionPairs(items, type); const descriptors = items.map((item, index) => createDescriptor(item, dirname, { type, diff --git a/packages/babel-core/src/config/full.js b/packages/babel-core/src/config/full.js index d38aec4ba698..940d1f914f9a 100644 --- a/packages/babel-core/src/config/full.js +++ b/packages/babel-core/src/config/full.js @@ -13,7 +13,11 @@ import { import type { UnloadedDescriptor } from "./config-descriptors"; import traverse from "@babel/traverse"; import { makeWeakCache, type CacheConfigurator } from "./caching"; -import { validate, type CallerMetadata } from "./validation/options"; +import { + validate, + type CallerMetadata, + checkNoUnwrappedItemOptionPairs, +} from "./validation/options"; import { validatePluginObject } from "./validation/plugins"; import makeAPI from "./helpers/config-api"; @@ -70,21 +74,54 @@ export default function loadFullConfig( }, pass: Array, ) { - const plugins = config.plugins.reduce((acc, descriptor) => { - if (descriptor.options !== false) { - acc.push(loadPluginDescriptor(descriptor, context)); - } - return acc; - }, []); - const presets = config.presets.reduce((acc, descriptor) => { - if (descriptor.options !== false) { - acc.push({ - preset: loadPresetDescriptor(descriptor, context), - pass: descriptor.ownPass ? [] : pass, - }); - } - return acc; - }, []); + const plugins = config.plugins.reduce( + (acc: Plugin[], descriptor: UnloadedDescriptor, i) => { + if (descriptor.options !== false) { + try { + acc.push(loadPluginDescriptor(descriptor, context)); + } catch (e) { + // print special message for `plugins: ["@babel/foo", { foo: "option" }]` + if (i > 0 && e.code === "BABEL_UNKNOWN_PLUGIN_PROPERTY") { + checkNoUnwrappedItemOptionPairs( + config.plugins[i - 1], + descriptor, + "plugin", + i, + e, + ); + } + throw e; + } + } + return acc; + }, + [], + ); + const presets = config.presets.reduce( + (acc, descriptor: UnloadedDescriptor, i) => { + if (descriptor.options !== false) { + try { + acc.push({ + preset: loadPresetDescriptor(descriptor, context), + pass: descriptor.ownPass ? [] : pass, + }); + } catch (e) { + if (i > 0 && e.code === "BABEL_UNKNOWN_OPTION") { + checkNoUnwrappedItemOptionPairs( + config.presets[i - 1], + descriptor, + "preset", + i, + e, + ); + } + throw e; + } + } + return acc; + }, + [], + ); // resolve presets if (presets.length > 0) { diff --git a/packages/babel-core/src/config/validation/options.js b/packages/babel-core/src/config/validation/options.js index 055ea1f42ae6..54f35165073d 100644 --- a/packages/babel-core/src/config/validation/options.js +++ b/packages/babel-core/src/config/validation/options.js @@ -27,7 +27,7 @@ import { type Validator, type OptionPath, } from "./option-assertions"; -import { validatePluginObject } from "./plugins"; +import type { UnloadedDescriptor } from "../config-descriptors"; const ROOT_VALIDATORS: ValidatorSet = { cwd: (assertString: Validator<$PropertyType>), @@ -371,11 +371,14 @@ function throwUnknownError(loc: OptionPath) { ); } else { // eslint-disable-next-line max-len - const unknownOptErr = `Unknown option: ${msg( - loc, - )}. Check out https://babeljs.io/docs/en/babel-core/#options for more information about options.`; + const unknownOptErr = new ReferenceError( + `Unknown option: ${msg( + loc, + )}. Check out https://babeljs.io/docs/en/babel-core/#options for more information about options.`, + ); + unknownOptErr.code = "BABEL_UNKNOWN_OPTION"; - throw new ReferenceError(unknownOptErr); + throw unknownOptErr; } } @@ -441,26 +444,25 @@ function assertOverridesList(loc: OptionPath, value: mixed): OverridesList { return (arr: any); } -export function assertNoUnwrappedItemOptionPairs( - items: PluginList, +export function checkNoUnwrappedItemOptionPairs( + lastItem: UnloadedDescriptor, + thisItem: UnloadedDescriptor, type: "plugin" | "preset", + index: number, + e: Error, ): void { if ( - items.length === 2 && - typeof items[0] === "string" && - typeof items[1] === "object" && - !Array.isArray(items[1]) + lastItem.file && + lastItem.options === undefined && + typeof thisItem.value === "object" ) { - try { - type === "preset" - ? validate(type, items[1]) - : validatePluginObject(items[1]); - } catch (e) { - throw new Error( - `.${type}[1] is not a valid ${type}. Maybe you meant to use\n` + - `"${type}": [\n ["${items[0]}", ${JSON.stringify(items[1])}]\n]\n` + - `To be a valid ${type}, its name and options should be wrapped in a pair of brackets`, - ); - } + e.message += + `\n- Maybe you meant to use\n` + + `"${type}": [\n ["${lastItem.file.request}", ${JSON.stringify( + thisItem.value, + undefined, + 2, + )}]\n]\n` + + `To be a valid ${type}, its name and options should be wrapped in a pair of brackets`; } } diff --git a/packages/babel-core/src/config/validation/plugins.js b/packages/babel-core/src/config/validation/plugins.js index ea1fc514306d..1990cb82dd83 100644 --- a/packages/babel-core/src/config/validation/plugins.js +++ b/packages/babel-core/src/config/validation/plugins.js @@ -97,7 +97,13 @@ export function validatePluginObject(obj: {}): PluginObject { }; if (validator) validator(optLoc, obj[key]); - else throw new Error(`.${key} is not a valid Plugin property`); + else { + const invalidPluginPropertyError = new Error( + `.${key} is not a valid Plugin property`, + ); + invalidPluginPropertyError.code = "BABEL_UNKNOWN_PLUGIN_PROPERTY"; + throw invalidPluginPropertyError; + } }); return (obj: any); diff --git a/packages/babel-core/test/__snapshots__/option-manager.js.snap b/packages/babel-core/test/__snapshots__/option-manager.js.snap index e710e54d9de9..89112efc6f88 100644 --- a/packages/babel-core/test/__snapshots__/option-manager.js.snap +++ b/packages/babel-core/test/__snapshots__/option-manager.js.snap @@ -1,7 +1,8 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`option-manager config plugin/preset flattening and overriding should throw when an option is provided as a plugin 1`] = ` -"[BABEL] unknown: .plugin[1] is not a valid plugin. Maybe you meant to use +"[BABEL] unknown: .useSpread is not a valid Plugin property +- Maybe you meant to use \\"plugin\\": [ [\\"./fixtures/option-manager/babel-plugin-foo\\", { \\"useSpread\\": true @@ -11,7 +12,8 @@ To be a valid plugin, its name and options should be wrapped in a pair of bracke `; exports[`option-manager config plugin/preset flattening and overriding should throw when an option is provided as a preset 1`] = ` -"[BABEL] unknown: .preset[1] is not a valid preset. Maybe you meant to use +"[BABEL] unknown: Unknown option: .useBuiltIns. Check out https://babeljs.io/docs/en/babel-core/#options for more information about options. +- Maybe you meant to use \\"preset\\": [ [\\"./fixtures/option-manager/babel-preset-bar\\", { \\"useBuiltIns\\": \\"entry\\"