diff --git a/packages/babel-cli/src/babel/index.js b/packages/babel-cli/src/babel/index.js index 530501e926..00fcdfbd77 100755 --- a/packages/babel-cli/src/babel/index.js +++ b/packages/babel-cli/src/babel/index.js @@ -242,6 +242,7 @@ delete opts.quiet; delete opts.configFile; delete opts.deleteDirOnStart; delete opts.keepFileExtension; +delete opts.relative; // Commander will default the "--no-" arguments to true, but we want to leave them undefined so that // @babel/core can handle the default-assignment logic on its own. diff --git a/packages/babel-core/src/config/build-config-chain.js b/packages/babel-core/src/config/build-config-chain.js index 56995ea6f2..5b4b8cee24 100644 --- a/packages/babel-core/src/config/build-config-chain.js +++ b/packages/babel-core/src/config/build-config-chain.js @@ -4,6 +4,12 @@ import { getEnv } from "./helpers/environment"; import path from "path"; import micromatch from "micromatch"; import buildDebug from "debug"; +import { + validate, + type ValidatedOptions, + type PluginList, + type IgnoreList, +} from "./options"; const debug = buildDebug("babel:config:config-chain"); @@ -11,26 +17,31 @@ import { findConfigs, loadConfig, type ConfigFile } from "./loading/files"; import { makeWeakCache, makeStrongCache } from "./caching"; -type ConfigItem = { - type: "options" | "arguments", - options: {}, - dirname: string, - alias: string, - loc: string, -}; - -type ConfigRaw = { - type: "options" | "arguments", - options: {}, +export type ConfigItem = { + type: "arguments" | "env" | "file", + options: ValidatedOptions, alias: string, dirname: string, }; -export default function buildConfigChain(opts: {}): Array | null { - if (typeof opts.filename !== "string" && opts.filename != null) { - throw new Error(".filename must be a string, null, or undefined"); - } +type ConfigPart = + | { + part: "config", + config: ConfigItem, + ignore: ?IgnoreList, + only: ?IgnoreList, + activeEnv: string | null, + } + | { + part: "extends", + path: string, + dirname: string, + activeEnv: string | null, + }; +export default function buildConfigChain( + opts: ValidatedOptions, +): Array | null { const filename = opts.filename ? path.resolve(opts.filename) : null; const builder = new ConfigChainBuilder( filename ? new LoadedFile(filename) : null, @@ -58,21 +69,34 @@ export default function buildConfigChain(opts: {}): Array | null { class ConfigChainBuilder { file: LoadedFile | null; configs: Array = []; + seenFiles: Set = new Set(); constructor(file: LoadedFile | null) { this.file = file; } - mergeConfigArguments(opts: {}, dirname: string, envKey: string) { + mergeConfigArguments( + opts: ValidatedOptions, + dirname: string, + envKey: string, + ) { flattenArgumentsOptionsParts(opts, dirname, envKey).forEach(part => this._processConfigPart(part, envKey), ); } mergeConfigFile(file: ConfigFile, envKey: string) { - flattenFileOptionsParts(file)(envKey).forEach(part => - this._processConfigPart(part, envKey), - ); + if (this.seenFiles.has(file)) { + throw new Error( + `Cycle detected in Babel configuration file through "${file.filepath}".`, + ); + } + + const parts = flattenFileOptionsParts(file)(envKey); + + this.seenFiles.add(file); + parts.forEach(part => this._processConfigPart(part, envKey)); + this.seenFiles.delete(file); } _processConfigPart(part: ConfigPart, envKey: string) { @@ -92,14 +116,7 @@ class ConfigChainBuilder { this.configs.push(part.config); } else { - const extendsConfig = loadConfig(part.path, part.dirname); - - const existingConfig = this.configs.some(config => { - return config.alias === extendsConfig.filepath; - }); - if (!existingConfig) { - this.mergeConfigFile(extendsConfig, envKey); - } + this.mergeConfigFile(loadConfig(part.path, part.dirname), envKey); } } } @@ -110,43 +127,26 @@ class ConfigChainBuilder { * object identity preserved between calls so that they can be used for caching. */ function flattenArgumentsOptionsParts( - opts: {}, + opts: ValidatedOptions, dirname: string, envKey: string, ): Array { + const { + env, + plugins, + presets, + passPerPreset, + extends: extendsPath, + ...options + } = opts; + const raw = []; - - const env = typeof opts.env === "object" ? opts.env : null; - const plugins = Array.isArray(opts.plugins) ? opts.plugins : null; - const presets = Array.isArray(opts.presets) ? opts.presets : null; - const passPerPreset = - typeof opts.passPerPreset === "boolean" ? opts.passPerPreset : false; - if (env) { raw.push(...flattenArgumentsEnvOptionsParts(env)(dirname)(envKey)); } - const innerOpts = Object.assign({}, opts); - // If the env, plugins, and presets values on the object aren't arrays or - // objects, leave them in the base opts so that normal options validation - // will throw errors on them later. - if (env) delete innerOpts.env; - if (plugins) delete innerOpts.plugins; - if (presets) { - delete innerOpts.presets; - delete innerOpts.passPerPreset; - } - delete innerOpts.extends; - - if (Object.keys(innerOpts).length > 0) { - raw.push( - ...flattenOptionsParts({ - type: "arguments", - options: innerOpts, - alias: "base", - dirname, - }), - ); + if (Object.keys(options).length > 0) { + raw.push(...flattenOptionsParts(buildArgumentsItem(options, dirname))); } if (plugins) { @@ -154,14 +154,14 @@ function flattenArgumentsOptionsParts( } if (presets) { raw.push( - ...flattenArgumentsPresetsOptionsParts(presets)(passPerPreset)(dirname), + ...flattenArgumentsPresetsOptionsParts(presets)(!!passPerPreset)(dirname), ); } - if (opts.extends != null) { + if (extendsPath != null) { raw.push( ...flattenOptionsParts( - buildArgumentsRawConfig({ extends: opts.extends }, dirname), + buildArgumentsItem({ extends: extendsPath }, dirname), ), ); } @@ -174,10 +174,10 @@ function flattenArgumentsOptionsParts( * the object identity of the 'env' object. */ const flattenArgumentsEnvOptionsParts = makeWeakCache((env: {}) => { - const options = { env }; + const options: ValidatedOptions = { env }; return makeStrongCache((dirname: string) => - flattenOptionsPartsLookup(buildArgumentsRawConfig(options, dirname)), + flattenOptionsPartsLookup(buildArgumentsItem(options, dirname)), ); }); @@ -186,11 +186,11 @@ const flattenArgumentsEnvOptionsParts = makeWeakCache((env: {}) => { * the object identity of the 'plugins' object. */ const flattenArgumentsPluginsOptionsParts = makeWeakCache( - (plugins: Array) => { - const options = { plugins }; + (plugins: PluginList) => { + const options: ValidatedOptions = { plugins }; return makeStrongCache((dirname: string) => - flattenOptionsParts(buildArgumentsRawConfig(options, dirname)), + flattenOptionsParts(buildArgumentsItem(options, dirname)), ); }, ); @@ -200,20 +200,23 @@ const flattenArgumentsPluginsOptionsParts = makeWeakCache( * the object identity of the 'presets' object. */ const flattenArgumentsPresetsOptionsParts = makeWeakCache( - (presets: Array) => - makeStrongCache((passPerPreset: ?boolean) => { + (presets: PluginList) => + makeStrongCache((passPerPreset: boolean) => { // The concept of passPerPreset is integrally tied to the preset list // so unfortunately we need to copy both values here, adding an extra // layer of caching functions. const options = { presets, passPerPreset }; return makeStrongCache((dirname: string) => - flattenOptionsParts(buildArgumentsRawConfig(options, dirname)), + flattenOptionsParts(buildArgumentsItem(options, dirname)), ); }), ); -function buildArgumentsRawConfig(options: {}, dirname: string): ConfigRaw { +function buildArgumentsItem( + options: ValidatedOptions, + dirname: string, +): ConfigItem { return { type: "arguments", options, @@ -229,8 +232,8 @@ function buildArgumentsRawConfig(options: {}, dirname: string): ConfigRaw { */ const flattenFileOptionsParts = makeWeakCache((file: ConfigFile) => { return flattenOptionsPartsLookup({ - type: "options", - options: file.options, + type: "file", + options: validate("file", file.options), alias: file.filepath, dirname: file.dirname, }); @@ -241,7 +244,7 @@ const flattenFileOptionsParts = makeWeakCache((file: ConfigFile) => { * the environment passed as the first argument. */ function flattenOptionsPartsLookup( - config: ConfigRaw, + config: ConfigItem, ): (string | null) => Array { const parts = flattenOptionsParts(config); @@ -263,107 +266,45 @@ function flattenOptionsPartsLookup( return envKey => lookup.get(envKey) || def; } -type ConfigPart = - | { - part: "config", - config: ConfigItem, - ignore: ?Array, - only: ?Array, - activeEnv: string | null, - } - | { - part: "extends", - path: string, - dirname: string, - activeEnv: string | null, - }; - /** * Given a generic config object, flatten it into its various parts so that * then can be cached and processed later. */ function flattenOptionsParts( - rawConfig: ConfigRaw, + config: ConfigItem, activeEnv: string | null = null, ): Array { - const { type, options: rawOpts, alias, dirname } = rawConfig; - - if (rawOpts.ignore != null && !Array.isArray(rawOpts.ignore)) { - throw new Error( - `.ignore should be an array, ${JSON.stringify(rawOpts.ignore)} given`, - ); - } - if (rawOpts.only != null && !Array.isArray(rawOpts.only)) { - throw new Error( - `.only should be an array, ${JSON.stringify(rawOpts.only)} given`, - ); - } - const ignore = rawOpts.ignore || null; - const only = rawOpts.only || null; + const { options: rawOpts, alias, dirname } = config; const parts = []; - if ( - rawOpts.env != null && - (typeof rawOpts.env !== "object" || Array.isArray(rawOpts.env)) - ) { - throw new Error(".env block must be an object, null, or undefined"); + if (rawOpts.env) { + for (const envKey of Object.keys(rawOpts.env)) { + if (rawOpts.env[envKey]) { + parts.push( + ...flattenOptionsParts( + { + type: "env", + options: rawOpts.env[envKey], + alias: alias + `.env.${envKey}`, + dirname, + }, + envKey, + ), + ); + } + } } - const rawEnv = rawOpts.env || {}; - - Object.keys(rawEnv).forEach(envKey => { - const envOpts = rawEnv[envKey]; - - if (envOpts !== undefined && activeEnv !== null && activeEnv !== envKey) { - throw new Error(`Unreachable .env[${envKey}] block detected`); - } - - if ( - envOpts != null && - (typeof envOpts !== "object" || Array.isArray(envOpts)) - ) { - throw new Error(".env[...] block must be an object, null, or undefined"); - } - - if (envOpts) { - parts.push( - ...flattenOptionsParts( - { - type, - options: envOpts, - alias: alias + `.env.${envKey}`, - dirname, - }, - envKey, - ), - ); - } - }); - - const options = Object.assign({}, rawOpts); - delete options.env; - delete options.extends; - parts.push({ part: "config", - config: { - type, - options, - alias, - loc: alias, - dirname, - }, - ignore, - only, + config, + ignore: rawOpts.ignore, + only: rawOpts.only, activeEnv, }); if (rawOpts.extends != null) { - if (typeof rawOpts.extends !== "string") { - throw new Error(".extends must be a string"); - } - parts.push({ part: "extends", path: rawOpts.extends, @@ -390,8 +331,8 @@ class LoadedFile { * Tests if a filename should be ignored based on "ignore" and "only" options. */ shouldIgnore( - ignore: ?Array, - only: ?Array, + ignore: ?IgnoreList, + only: ?IgnoreList, dirname: string, ): boolean { if (ignore) { @@ -425,7 +366,7 @@ class LoadedFile { * Returns result of calling function with filename if pattern is a function. * Otherwise returns result of matching pattern Regex with filename. */ - _matchesPatterns(patterns: Array, dirname: string): boolean { + _matchesPatterns(patterns: IgnoreList, dirname: string): boolean { const res = []; const strings = []; const fns = []; @@ -433,12 +374,7 @@ class LoadedFile { patterns.forEach(pattern => { if (typeof pattern === "string") strings.push(pattern); else if (typeof pattern === "function") fns.push(pattern); - else if (pattern instanceof RegExp) res.push(pattern); - else { - throw new Error( - "Patterns must be a string, function, or regular expression", - ); - } + else res.push(pattern); }); const filename = this.filename; diff --git a/packages/babel-core/src/config/caching.js b/packages/babel-core/src/config/caching.js index 052e2803c3..bd12bc31ac 100644 --- a/packages/babel-core/src/config/caching.js +++ b/packages/babel-core/src/config/caching.js @@ -31,7 +31,7 @@ export function makeStrongCache( * configures its caching behavior. Cached values are stored weakly and the function argument must be * an object type. */ -export function makeWeakCache, ResultT>( +export function makeWeakCache | $ReadOnlyArray<*>, ResultT>( handler: (ArgT, CacheConfigurator) => ResultT, autoPermacache?: boolean, ): ArgT => ResultT { diff --git a/packages/babel-core/src/config/index.js b/packages/babel-core/src/config/index.js index 2676d24047..8e458cdf7d 100644 --- a/packages/babel-core/src/config/index.js +++ b/packages/babel-core/src/config/index.js @@ -16,7 +16,7 @@ export type PluginPasses = Array; * Standard API for loading Babel configuration data. Not for public consumption. */ export default function loadConfig(opts: mixed): ResolvedConfig | null { - if (opts != null && typeof opts !== "object") { + if (opts != null && (typeof opts !== "object" || Array.isArray(opts))) { throw new Error("Babel options must be an object, null, or undefined"); } diff --git a/packages/babel-core/src/config/option-assertions.js b/packages/babel-core/src/config/option-assertions.js new file mode 100644 index 0000000000..01c65e4e14 --- /dev/null +++ b/packages/babel-core/src/config/option-assertions.js @@ -0,0 +1,179 @@ +// @flow + +import type { + IgnoreList, + IgnoreItem, + PluginList, + PluginItem, + PluginTarget, + SourceMapsOption, + SourceTypeOption, + CompactOption, + RootInputSourceMapOption, +} from "./options"; + +export function assertSourceMaps( + key: string, + value: mixed, +): SourceMapsOption | void { + if ( + value !== undefined && + typeof value !== "boolean" && + value !== "inline" && + value !== "both" + ) { + throw new Error( + `.${key} must be a boolean, "inline", "both", or undefined`, + ); + } + return value; +} + +export function assertCompact(key: string, value: mixed): CompactOption | void { + if (value !== undefined && typeof value !== "boolean" && value !== "auto") { + throw new Error(`.${key} must be a boolean, "auto", or undefined`); + } + return value; +} + +export function assertSourceType( + key: string, + value: mixed, +): SourceTypeOption | void { + if (value !== undefined && value !== "module" && value !== "script") { + throw new Error(`.${key} must be "module", "script", or undefined`); + } + return value; +} + +export function assertInputSourceMap( + key: string, + value: mixed, +): RootInputSourceMapOption | void { + if ( + value !== undefined && + typeof value !== "boolean" && + (typeof value !== "object" || !value) + ) { + throw new Error(".inputSourceMap must be a boolean, object, or undefined"); + } + return value; +} + +export function assertString(key: string, value: mixed): string | void { + if (value !== undefined && typeof value !== "string") { + throw new Error(`.${key} must be a string, or undefined`); + } + return value; +} + +export function assertFunction(key: string, value: mixed): Function | void { + if (value !== undefined && typeof value !== "function") { + throw new Error(`.${key} must be a function, or undefined`); + } + return value; +} + +export function assertBoolean(key: string, value: mixed): boolean | void { + if (value !== undefined && typeof value !== "boolean") { + throw new Error(`.${key} must be a boolean, or undefined`); + } + return value; +} + +export function assertObject(key: string, value: mixed): {} | void { + if ( + value !== undefined && + (typeof value !== "object" || Array.isArray(value) || !value) + ) { + throw new Error(`.${key} must be an object, or undefined`); + } + return value; +} + +export function assertIgnoreList(key: string, value: mixed): IgnoreList | void { + const arr = assertArray(key, value); + if (arr) { + arr.forEach((item, i) => assertIgnoreItem(key, i, item)); + } + return (arr: any); +} +function assertIgnoreItem( + key: string, + index: number, + value: mixed, +): IgnoreItem { + if ( + typeof value !== "string" && + typeof value !== "function" && + !(value instanceof RegExp) + ) { + throw new Error( + `.${key}[${index}] must be an array of string/Funtion/RegExp values, or or undefined`, + ); + } + return value; +} + +export function assertPluginList(key: string, value: mixed): PluginList | void { + const arr = assertArray(key, value); + if (arr) { + // Loop instead of using `.map` in order to preserve object identity + // for plugin array for use during config chain processing. + arr.forEach((item, i) => assertPluginItem(key, i, item)); + } + return (arr: any); +} +function assertPluginItem( + key: string, + index: number, + value: mixed, +): PluginItem { + if (Array.isArray(value)) { + if (value.length === 0) { + throw new Error(`.${key}[${index}] must include an object`); + } + if (value.length > 2) { + throw new Error(`.${key}[${index}] may only be a two-tuple`); + } + + assertPluginTarget(key, index, true, value[0]); + + if (value.length === 2) { + const opts = value[1]; + if (opts != null && (typeof opts !== "object" || Array.isArray(opts))) { + throw new Error(`.${key}[${index}][1] must be an object, or undefined`); + } + } + } else { + assertPluginTarget(key, index, false, value); + } + + return (value: any); +} +function assertPluginTarget( + key: string, + index: number, + inArray: boolean, + value: mixed, +): PluginTarget { + if ( + (typeof value !== "object" || !value) && + typeof value !== "string" && + typeof value !== "function" + ) { + throw new Error( + `.${key}[${index}]${inArray + ? `[0]` + : ""} must be a string, object, function`, + ); + } + return value; +} + +function assertArray(key: string, value: mixed): ?$ReadOnlyArray { + if (value != null && !Array.isArray(value)) { + throw new Error(`.${key} must be an array, or undefined`); + } + return value; +} diff --git a/packages/babel-core/src/config/option-manager.js b/packages/babel-core/src/config/option-manager.js index 787fd28ed1..641440c353 100644 --- a/packages/babel-core/src/config/option-manager.js +++ b/packages/babel-core/src/config/option-manager.js @@ -4,13 +4,13 @@ import * as context from "../index"; import Plugin from "./plugin"; import defaults from "lodash/defaults"; import merge from "lodash/merge"; -import removed from "./removed"; -import buildConfigChain from "./build-config-chain"; +import buildConfigChain, { type ConfigItem } from "./build-config-chain"; import path from "path"; import traverse from "@babel/traverse"; import clone from "lodash/clone"; import { makeWeakCache } from "./caching"; import { getEnv } from "./helpers/environment"; +import { validate, type ValidatedOptions, type PluginItem } from "./options"; import { loadPlugin, @@ -19,54 +19,14 @@ import { loadGenerator, } from "./loading/files"; -type MergeOptions = { - +type: "arguments" | "options" | "preset", - options: {}, - alias: string, - loc: string, - dirname: string, -}; - -const optionNames = new Set([ - "relative", - "filename", - "filenameRelative", - "inputSourceMap", - "env", - "mode", - "retainLines", - "highlightCode", - "suppressDeprecationMessages", - "presets", - "plugins", - "ignore", - "only", - "code", - "ast", - "extends", - "comments", - "shouldPrintComment", - "wrapPluginVisitorMethod", - "compact", - "minified", - "sourceMaps", - "sourceMapTarget", - "sourceFileName", - "sourceRoot", - "babelrc", - "sourceType", - "auxiliaryCommentBefore", - "auxiliaryCommentAfter", - "getModuleId", - "moduleRoot", - "moduleIds", - "moduleId", - "passPerPreset", - // Deprecate top level parserOpts - "parserOpts", - // Deprecate top level generatorOpts - "generatorOpts", -]); +type MergeOptions = + | ConfigItem + | { + type: "preset", + options: ValidatedOptions, + alias: string, + dirname: string, + }; const ALLOWED_PLUGIN_KEYS = new Set([ "name", @@ -86,11 +46,11 @@ export default function manageOptions(opts: {}): { class OptionManager { constructor() { - this.options = createInitialOptions(); + this.options = {}; this.passes = [[]]; } - options: Object; + options: ValidatedOptions; passes: Array>; /** @@ -112,12 +72,6 @@ class OptionManager { loadPresetDescriptor(descriptor), ); - if ( - config.options.passPerPreset != null && - typeof config.options.passPerPreset !== "boolean" - ) { - throw new Error(".passPerPreset must be a boolean or undefined"); - } const passPerPreset = config.options.passPerPreset; pass = pass || this.passes[0]; @@ -141,11 +95,27 @@ class OptionManager { pass.unshift(...plugins); } - merge(this.options, result.options); + const options = Object.assign({}, result.options); + delete options.extends; + delete options.env; + delete options.plugins; + delete options.presets; + delete options.passPerPreset; + + // "sourceMap" is just aliased to sourceMap, so copy it over as + // we merge the options together. + if (options.sourceMap) { + options.sourceMaps = options.sourceMap; + delete options.sourceMap; + } + + merge(this.options, options); } - init(opts: {}) { - const configChain = buildConfigChain(opts); + init(inputOpts: {}) { + const args = validate("arguments", inputOpts); + + const configChain = buildConfigChain(args); if (!configChain) return null; try { @@ -156,15 +126,13 @@ class OptionManager { // There are a few case where thrown errors will try to annotate themselves multiple times, so // to keep things simple we just bail out if re-wrapping the message. if (!/^\[BABEL\]/.test(e.message)) { - const filename = - typeof opts.filename === "string" ? opts.filename : null; - e.message = `[BABEL] ${filename || "unknown"}: ${e.message}`; + e.message = `[BABEL] ${args.filename || "unknown"}: ${e.message}`; } throw e; } - opts = this.options; + const opts: Object = merge(createInitialOptions(), this.options); // Tack the passes onto the object itself so that, if this object is passed back to Babel a second time, // it will be in the right structure to not change behavior. @@ -173,6 +141,7 @@ class OptionManager { .slice(1) .filter(plugins => plugins.length > 0) .map(plugins => ({ plugins })); + opts.passPerPreset = opts.presets.length > 0; if (opts.inputSourceMap) { opts.sourceMaps = true; @@ -217,7 +186,6 @@ type BasicDescriptor = { options: {} | void, dirname: string, alias: string, - loc: string, }; type LoadedDescriptor = { @@ -225,64 +193,31 @@ type LoadedDescriptor = { options: {}, dirname: string, alias: string, - loc: string, }; /** * Load and validate the given config into a set of options, plugins, and presets. */ -const loadConfig = makeWeakCache((config): { +const loadConfig = makeWeakCache((config: MergeOptions): { options: {}, plugins: Array, presets: Array, } => { const options = normalizeOptions(config); - if ( - config.options.plugins != null && - !Array.isArray(config.options.plugins) - ) { - throw new Error(".plugins should be an array, null, or undefined"); - } + const plugins = (config.options.plugins || []).map((plugin, index) => + createDescriptor(plugin, loadPlugin, config.dirname, { + index, + alias: config.alias, + }), + ); - const plugins = (config.options.plugins || []).map((plugin, index) => { - const { filepath, value, options } = normalizePair( - plugin, - loadPlugin, - config.dirname, - ); - - return { - alias: filepath || `${config.loc}$${index}`, - loc: filepath || config.loc, - value, - options, - dirname: config.dirname, - }; - }); - - if ( - config.options.presets != null && - !Array.isArray(config.options.presets) - ) { - throw new Error(".presets should be an array, null, or undefined"); - } - - const presets = (config.options.presets || []).map((preset, index) => { - const { filepath, value, options } = normalizePair( - preset, - loadPreset, - config.dirname, - ); - - return { - alias: filepath || `${config.loc}$${index}`, - loc: filepath || config.loc, - value, - options, - dirname: config.dirname, - }; - }); + const presets = (config.options.presets || []).map((preset, index) => + createDescriptor(preset, loadPreset, config.dirname, { + index, + alias: config.alias, + }), + ); return { options, plugins, presets }; }); @@ -292,7 +227,7 @@ const loadConfig = makeWeakCache((config): { */ const loadDescriptor = makeWeakCache( ( - { value, options = {}, dirname, alias, loc }: BasicDescriptor, + { value, options = {}, dirname, alias }: BasicDescriptor, cache, ): LoadedDescriptor => { let item = value; @@ -316,7 +251,7 @@ const loadDescriptor = makeWeakCache( throw new Error("Plugin/Preset did not return an object."); } - return { value: item, options, dirname, alias, loc }; + return { value: item, options, dirname, alias }; }, ); @@ -339,7 +274,7 @@ function loadPluginDescriptor(descriptor: BasicDescriptor): Plugin { const instantiatePlugin = makeWeakCache( ( - { value: pluginObj, options, dirname, alias, loc }: LoadedDescriptor, + { value: pluginObj, options, dirname, alias }: LoadedDescriptor, cache, ): Plugin => { Object.keys(pluginObj).forEach(key => { @@ -369,8 +304,7 @@ const instantiatePlugin = makeWeakCache( let inherits; if (plugin.inherits) { inheritsDescriptor = { - alias: `${loc}$inherits`, - loc, + alias: `${alias}$inherits`, value: plugin.inherits, options, dirname, @@ -405,12 +339,11 @@ const loadPresetDescriptor = (descriptor: BasicDescriptor): MergeOptions => { }; const instantiatePreset = makeWeakCache( - ({ value, dirname, alias, loc }: LoadedDescriptor): MergeOptions => { + ({ value, dirname, alias }: LoadedDescriptor): MergeOptions => { return { type: "preset", - options: value, + options: validate("preset", value), alias, - loc, dirname, }; }, @@ -420,72 +353,12 @@ const instantiatePreset = makeWeakCache( * Validate and return the options object for the config. */ function normalizeOptions(config) { - const alias = config.alias || "foreign"; - const type = config.type; - - // - if (typeof config.options !== "object" || Array.isArray(config.options)) { - throw new TypeError(`Invalid options type for ${alias}`); - } - // const options = Object.assign({}, config.options); - if (type !== "arguments") { - if (options.filename !== undefined) { - throw new Error(`${alias}.filename is only allowed as a root argument`); - } - - if (options.babelrc !== undefined) { - throw new Error(`${alias}.babelrc is only allowed as a root argument`); - } - } - - if (type === "preset") { - if (options.only !== undefined) { - throw new Error(`${alias}.only is not supported in a preset`); - } - if (options.ignore !== undefined) { - throw new Error(`${alias}.ignore is not supported in a preset`); - } - if (options.extends !== undefined) { - throw new Error(`${alias}.extends is not supported in a preset`); - } - if (options.env !== undefined) { - throw new Error(`${alias}.env is not supported in a preset`); - } - } - - if (options.sourceMap !== undefined) { - if (options.sourceMaps !== undefined) { - throw new Error(`Both ${alias}.sourceMap and .sourceMaps have been set`); - } - - options.sourceMaps = options.sourceMap; - delete options.sourceMap; - } - - for (const key in options) { - // check for an unknown option - if (!optionNames.has(key)) { - if (removed[key]) { - const { message, version = 5 } = removed[key]; - - throw new ReferenceError( - `Using removed Babel ${version} option: ${alias}.${key} - ${message}`, - ); - } else { - // eslint-disable-next-line max-len - const unknownOptErr = `Unknown option: ${alias}.${key}. Check out http://babeljs.io/docs/usage/options/ for more information about options.`; - - throw new ReferenceError(unknownOptErr); - } - } - } - if (options.parserOpts && typeof options.parserOpts.parser === "string") { options.parserOpts = Object.assign({}, options.parserOpts); - options.parserOpts.parser = loadParser( + (options.parserOpts: any).parser = loadParser( options.parserOpts.parser, config.dirname, ).value; @@ -496,41 +369,34 @@ function normalizeOptions(config) { typeof options.generatorOpts.generator === "string" ) { options.generatorOpts = Object.assign({}, options.generatorOpts); - options.generatorOpts.generator = loadGenerator( + (options.generatorOpts: any).generator = loadGenerator( options.generatorOpts.generator, config.dirname, ).value; } - delete options.passPerPreset; - delete options.plugins; - delete options.presets; - return options; } /** * Given a plugin/preset item, resolve it into a standard format. */ -function normalizePair( - pair: mixed, +function createDescriptor( + pair: PluginItem, resolver, dirname, -): { - filepath: string | null, - value: {} | Function, - options: {} | void, -} { + { + index, + alias, + }: { + index: number, + alias: string, + }, +): BasicDescriptor { let options; let value = pair; - if (Array.isArray(pair)) { - if (pair.length > 2) { - throw new Error( - `Unexpected extra options ${JSON.stringify(pair.slice(2))}.`, - ); - } - - [value, options] = pair; + if (Array.isArray(value)) { + [value, options] = value; } let filepath = null; @@ -572,7 +438,12 @@ function normalizePair( } options = options || undefined; - return { filepath, value, options }; + return { + alias: filepath || `${alias}$${index}`, + value, + options, + dirname, + }; } function chain(a, b) { diff --git a/packages/babel-core/src/config/options.js b/packages/babel-core/src/config/options.js new file mode 100644 index 0000000000..c46ca0d3df --- /dev/null +++ b/packages/babel-core/src/config/options.js @@ -0,0 +1,255 @@ +// @flow + +import removed from "./removed"; +import { + assertString, + assertBoolean, + assertObject, + assertInputSourceMap, + assertIgnoreList, + assertPluginList, + assertFunction, + assertSourceMaps, + assertCompact, + assertSourceType, +} from "./option-assertions"; + +type ValidatorSet = { + [string]: Validator, +}; + +type Validator = (string, mixed) => T; + +const ROOT_VALIDATORS: ValidatorSet = { + filename: (assertString: Validator< + $PropertyType, + >), + filenameRelative: (assertString: Validator< + $PropertyType, + >), + babelrc: (assertBoolean: Validator< + $PropertyType, + >), + code: (assertBoolean: Validator<$PropertyType>), + ast: (assertBoolean: Validator<$PropertyType>), +}; + +const NONPRESET_VALIDATORS: ValidatorSet = { + extends: (assertString: Validator< + $PropertyType, + >), + env: (assertEnvSet: Validator<$PropertyType>), + ignore: (assertIgnoreList: Validator< + $PropertyType, + >), + only: (assertIgnoreList: Validator<$PropertyType>), +}; + +const COMMON_VALIDATORS: ValidatorSet = { + // TODO: Should 'inputSourceMap' be moved to be a root-only option? + // We may want a boolean-only version to be a common option, with the + // object only allowed as a root config argument. + inputSourceMap: (assertInputSourceMap: Validator< + $PropertyType, + >), + presets: (assertPluginList: Validator< + $PropertyType, + >), + plugins: (assertPluginList: Validator< + $PropertyType, + >), + passPerPreset: (assertBoolean: Validator< + $PropertyType, + >), + retainLines: (assertBoolean: Validator< + $PropertyType, + >), + comments: (assertBoolean: Validator< + $PropertyType, + >), + shouldPrintComment: (assertFunction: Validator< + $PropertyType, + >), + compact: (assertCompact: Validator< + $PropertyType, + >), + minified: (assertBoolean: Validator< + $PropertyType, + >), + auxiliaryCommentBefore: (assertString: Validator< + $PropertyType, + >), + auxiliaryCommentAfter: (assertString: Validator< + $PropertyType, + >), + sourceType: (assertSourceType: Validator< + $PropertyType, + >), + wrapPluginVisitorMethod: (assertFunction: Validator< + $PropertyType, + >), + highlightCode: (assertBoolean: Validator< + $PropertyType, + >), + sourceMaps: (assertSourceMaps: Validator< + $PropertyType, + >), + sourceMap: (assertSourceMaps: Validator< + $PropertyType, + >), + sourceMapTarget: (assertString: Validator< + $PropertyType, + >), + sourceFileName: (assertString: Validator< + $PropertyType, + >), + sourceRoot: (assertString: Validator< + $PropertyType, + >), + getModuleId: (assertFunction: Validator< + $PropertyType, + >), + moduleRoot: (assertString: Validator< + $PropertyType, + >), + moduleIds: (assertBoolean: Validator< + $PropertyType, + >), + moduleId: (assertString: Validator< + $PropertyType, + >), + parserOpts: (assertObject: Validator< + $PropertyType, + >), + generatorOpts: (assertObject: Validator< + $PropertyType, + >), +}; +export type ValidatedOptions = { + filename?: string, + filenameRelative?: string, + babelrc?: boolean, + code?: boolean, + ast?: boolean, + inputSourceMap?: RootInputSourceMapOption, + + extends?: string, + env?: EnvSet, + ignore?: IgnoreList, + only?: IgnoreList, + + presets?: PluginList, + plugins?: PluginList, + passPerPreset?: boolean, + + // Options for @babel/generator + retainLines?: boolean, + comments?: boolean, + shouldPrintComment?: Function, + compact?: CompactOption, + minified?: boolean, + auxiliaryCommentBefore?: string, + auxiliaryCommentAfter?: string, + + // Parser + sourceType?: SourceTypeOption, + + wrapPluginVisitorMethod?: Function, + highlightCode?: boolean, + + // Sourcemap generation options. + sourceMaps?: SourceMapsOption, + sourceMap?: SourceMapsOption, + sourceMapTarget?: string, + sourceFileName?: string, + sourceRoot?: string, + + // AMD/UMD/SystemJS module naming options. + getModuleId?: Function, + moduleRoot?: string, + moduleIds?: boolean, + moduleId?: string, + + // Deprecate top level parserOpts + parserOpts?: {}, + // Deprecate top level generatorOpts + generatorOpts?: {}, +}; + +export type EnvSet = { + [string]: ?T, +}; +export type IgnoreItem = string | Function | RegExp; +export type IgnoreList = $ReadOnlyArray; + +export type PluginTarget = string | {} | Function; +export type PluginItem = PluginTarget | [PluginTarget, {} | void]; +export type PluginList = $ReadOnlyArray; + +export type SourceMapsOption = boolean | "inline" | "both"; +export type SourceTypeOption = "module" | "script"; +export type CompactOption = boolean | "auto"; +export type RootInputSourceMapOption = {} | boolean; + +export type OptionsType = "arguments" | "file" | "env" | "preset"; + +export function validate(type: OptionsType, opts: {}): ValidatedOptions { + assertNoDuplicateSourcemap(opts); + + Object.keys(opts).forEach(key => { + if (type === "preset" && NONPRESET_VALIDATORS[key]) { + throw new Error(`.${key} is not allowed in preset options`); + } + if (type !== "arguments" && ROOT_VALIDATORS[key]) { + throw new Error(`.${key} is only allowed in root programmatic options`); + } + + const validator = + COMMON_VALIDATORS[key] || + NONPRESET_VALIDATORS[key] || + ROOT_VALIDATORS[key]; + + if (validator) validator(key, opts[key]); + else throw buildUnknownError(key); + }); + + return (opts: any); +} + +function buildUnknownError(key: string) { + if (removed[key]) { + const { message, version = 5 } = removed[key]; + + throw new ReferenceError( + `Using removed Babel ${version} option: .${key} - ${message}`, + ); + } else { + // eslint-disable-next-line max-len + const unknownOptErr = `Unknown option: .${key}. Check out http://babeljs.io/docs/usage/options/ for more information about options.`; + + throw new ReferenceError(unknownOptErr); + } +} + +function has(obj: {}, key: string) { + return Object.prototype.hasOwnProperty.call(obj, key); +} + +function assertNoDuplicateSourcemap(opts: {}): void { + if (has(opts, "sourceMap") && has(opts, "sourceMaps")) { + throw new Error(".sourceMap is an alias for .sourceMaps, cannot use both"); + } +} + +function assertEnvSet(key: string, value: mixed): EnvSet { + const obj = assertObject(key, value); + if (obj) { + // Validate but don't copy the .env object in order to preserve + // object identity for use during config chain processing. + for (const key of Object.keys(obj)) { + const env = assertObject(key, obj[key]); + if (env) validate("env", env); + } + } + return (obj: any); +} diff --git a/packages/babel-core/test/api.js b/packages/babel-core/test/api.js index 1ab0ece980..cad0308862 100644 --- a/packages/babel-core/test/api.js +++ b/packages/babel-core/test/api.js @@ -145,7 +145,7 @@ describe("api", function() { babel.transform("", { plugins: [__dirname + "/../../babel-plugin-syntax-jsx", false], }); - }, /Error: \[BABEL\] unknown: Unexpected falsy value: false/); + }, /.plugins\[1\] must be a string, object, function/); }); it("options merge backwards", function() { @@ -530,31 +530,31 @@ describe("api", function() { it("default", function() { const result = babel.transform("foo;", { env: { - development: { code: false }, + development: { comments: false }, }, }); - assert.equal(result.code, undefined); + assert.equal(result.options.comments, false); }); it("BABEL_ENV", function() { process.env.BABEL_ENV = "foo"; const result = babel.transform("foo;", { env: { - foo: { code: false }, + foo: { comments: false }, }, }); - assert.equal(result.code, undefined); + assert.equal(result.options.comments, false); }); it("NODE_ENV", function() { process.env.NODE_ENV = "foo"; const result = babel.transform("foo;", { env: { - foo: { code: false }, + foo: { comments: false }, }, }); - assert.equal(result.code, undefined); + assert.equal(result.options.comments, false); }); }); diff --git a/packages/babel-core/test/config-chain.js b/packages/babel-core/test/config-chain.js index 23c8d95a57..097112cb07 100644 --- a/packages/babel-core/test/config-chain.js +++ b/packages/babel-core/test/config-chain.js @@ -72,7 +72,6 @@ describe("buildConfigChain", function() { ], }, alias: "base", - loc: "base", dirname: base(), }, ]; @@ -117,7 +116,6 @@ describe("buildConfigChain", function() { ], }, alias: "base", - loc: "base", dirname: base(), }, ]; @@ -374,30 +372,28 @@ describe("buildConfigChain", function() { const expected = [ { - type: "options", + type: "file", options: { plugins: ["extended"], }, alias: fixture("extended.babelrc.json"), - loc: fixture("extended.babelrc.json"), dirname: fixture(), }, { - type: "options", + type: "file", options: { + extends: "./extended.babelrc.json", plugins: ["root"], }, alias: fixture(".babelrc"), - loc: fixture(".babelrc"), dirname: fixture(), }, { - type: "options", + type: "file", options: { ignore: ["root-ignore"], }, alias: fixture(".babelignore"), - loc: fixture(".babelignore"), dirname: fixture(), }, { @@ -406,7 +402,6 @@ describe("buildConfigChain", function() { filename: fixture("dir1", "src.js"), }, alias: "base", - loc: "base", dirname: base(), }, ]; @@ -421,21 +416,19 @@ describe("buildConfigChain", function() { const expected = [ { - type: "options", + type: "file", options: { ignore: ["root-ignore"], }, alias: fixture(".babelignore"), - loc: fixture(".babelignore"), dirname: fixture(), }, { - type: "options", + type: "file", options: { plugins: ["dir2"], }, alias: fixture("dir2", ".babelrc"), - loc: fixture("dir2", ".babelrc"), dirname: fixture("dir2"), }, { @@ -444,7 +437,6 @@ describe("buildConfigChain", function() { filename: fixture("dir2", "src.js"), }, alias: "base", - loc: "base", dirname: base(), }, ]; @@ -459,30 +451,28 @@ describe("buildConfigChain", function() { const expected = [ { - type: "options", + type: "file", options: { plugins: ["extended"], }, alias: fixture("extended.babelrc.json"), - loc: fixture("extended.babelrc.json"), dirname: fixture(), }, { - type: "options", + type: "file", options: { + extends: "./extended.babelrc.json", plugins: ["root"], }, alias: fixture(".babelrc"), - loc: fixture(".babelrc"), dirname: fixture(), }, { - type: "options", + type: "file", options: { ignore: ["root-ignore"], }, alias: fixture(".babelignore"), - loc: fixture(".babelignore"), dirname: fixture(), }, { @@ -491,7 +481,6 @@ describe("buildConfigChain", function() { filename: fixture("dir3", "src.js"), }, alias: "base", - loc: "base", dirname: base(), }, ]; @@ -506,21 +495,27 @@ describe("buildConfigChain", function() { const expected = [ { - type: "options", + type: "file", options: { ignore: ["root-ignore"], }, alias: fixture(".babelignore"), - loc: fixture(".babelignore"), dirname: fixture(), }, { - type: "options", + type: "file", options: { + env: { + bar: { + plugins: ["env-bar"], + }, + foo: { + plugins: ["env-foo"], + }, + }, plugins: ["env-base"], }, alias: fixture("env", ".babelrc"), - loc: fixture("env", ".babelrc"), dirname: fixture("env"), }, { @@ -529,7 +524,6 @@ describe("buildConfigChain", function() { filename: fixture("env", "src.js"), }, alias: "base", - loc: "base", dirname: base(), }, ]; @@ -546,30 +540,35 @@ describe("buildConfigChain", function() { const expected = [ { - type: "options", + type: "file", options: { ignore: ["root-ignore"], }, alias: fixture(".babelignore"), - loc: fixture(".babelignore"), dirname: fixture(), }, { - type: "options", + type: "file", options: { + env: { + bar: { + plugins: ["env-bar"], + }, + foo: { + plugins: ["env-foo"], + }, + }, plugins: ["env-base"], }, alias: fixture("env", ".babelrc"), - loc: fixture("env", ".babelrc"), dirname: fixture("env"), }, { - type: "options", + type: "env", options: { plugins: ["env-foo"], }, alias: fixture("env", ".babelrc.env.foo"), - loc: fixture("env", ".babelrc.env.foo"), dirname: fixture("env"), }, { @@ -578,7 +577,6 @@ describe("buildConfigChain", function() { filename: fixture("env", "src.js"), }, alias: "base", - loc: "base", dirname: base(), }, ]; @@ -596,30 +594,35 @@ describe("buildConfigChain", function() { const expected = [ { - type: "options", + type: "file", options: { ignore: ["root-ignore"], }, alias: fixture(".babelignore"), - loc: fixture(".babelignore"), dirname: fixture(), }, { - type: "options", + type: "file", options: { + env: { + bar: { + plugins: ["env-bar"], + }, + foo: { + plugins: ["env-foo"], + }, + }, plugins: ["env-base"], }, alias: fixture("env", ".babelrc"), - loc: fixture("env", ".babelrc"), dirname: fixture("env"), }, { - type: "options", + type: "env", options: { plugins: ["env-bar"], }, alias: fixture("env", ".babelrc.env.bar"), - loc: fixture("env", ".babelrc.env.bar"), dirname: fixture("env"), }, { @@ -628,7 +631,6 @@ describe("buildConfigChain", function() { filename: fixture("env", "src.js"), }, alias: "base", - loc: "base", dirname: base(), }, ]; @@ -645,21 +647,19 @@ describe("buildConfigChain", function() { const expected = [ { - type: "options", + type: "file", options: { plugins: ["pkg-plugin"], }, alias: fixture("pkg", "package.json"), - loc: fixture("pkg", "package.json"), dirname: fixture("pkg"), }, { - type: "options", + type: "file", options: { ignore: ["pkg-ignore"], }, alias: fixture("pkg", ".babelignore"), - loc: fixture("pkg", ".babelignore"), dirname: fixture("pkg"), }, { @@ -668,7 +668,6 @@ describe("buildConfigChain", function() { filename: fixture("pkg", "src.js"), }, alias: "base", - loc: "base", dirname: base(), }, ]; @@ -683,21 +682,19 @@ describe("buildConfigChain", function() { const expected = [ { - type: "options", + type: "file", options: { ignore: ["root-ignore"], }, alias: fixture(".babelignore"), - loc: fixture(".babelignore"), dirname: fixture(), }, { - type: "options", + type: "file", options: { plugins: ["foo", "bar"], }, alias: fixture("js-config", ".babelrc.js"), - loc: fixture("js-config", ".babelrc.js"), dirname: fixture("js-config"), }, { @@ -706,7 +703,6 @@ describe("buildConfigChain", function() { filename: fixture("js-config", "src.js"), }, alias: "base", - loc: "base", dirname: base(), }, ]; @@ -721,21 +717,19 @@ describe("buildConfigChain", function() { const expected = [ { - type: "options", + type: "file", options: { ignore: ["root-ignore"], }, alias: fixture(".babelignore"), - loc: fixture(".babelignore"), dirname: fixture(), }, { - type: "options", + type: "file", options: { compact: true, }, alias: fixture("js-config-function", ".babelrc.js"), - loc: fixture("js-config-function", ".babelrc.js"), dirname: fixture("js-config-function"), }, { @@ -744,7 +738,6 @@ describe("buildConfigChain", function() { filename: fixture("js-config-function", "src.js"), }, alias: "base", - loc: "base", dirname: base(), }, ]; @@ -759,21 +752,19 @@ describe("buildConfigChain", function() { const expected = [ { - type: "options", + type: "file", options: { ignore: ["root-ignore"], }, alias: fixture(".babelignore"), - loc: fixture(".babelignore"), dirname: fixture(), }, { - type: "options", + type: "file", options: { plugins: ["foo", "bar"], }, alias: fixture("js-config-default", ".babelrc.js"), - loc: fixture("js-config-default", ".babelrc.js"), dirname: fixture("js-config-default"), }, { @@ -782,7 +773,6 @@ describe("buildConfigChain", function() { filename: fixture("js-config-default", "src.js"), }, alias: "base", - loc: "base", dirname: base(), }, ]; @@ -796,30 +786,28 @@ describe("buildConfigChain", function() { const expected = [ { - type: "options", + type: "file", options: { ignore: ["root-ignore"], }, alias: fixture(".babelignore"), - loc: fixture(".babelignore"), dirname: fixture(), }, { - type: "options", + type: "file", options: { plugins: ["extended"], }, alias: fixture("extended.babelrc.json"), - loc: fixture("extended.babelrc.json"), dirname: fixture(), }, { - type: "options", + type: "file", options: { + extends: "../extended.babelrc.json", plugins: ["foo", "bar"], }, alias: fixture("js-config-extended", ".babelrc.js"), - loc: fixture("js-config-extended", ".babelrc.js"), dirname: fixture("js-config-extended"), }, { @@ -828,7 +816,6 @@ describe("buildConfigChain", function() { filename: fixture("js-config-extended", "src.js"), }, alias: "base", - loc: "base", dirname: base(), }, ]; @@ -846,21 +833,19 @@ describe("buildConfigChain", function() { const expected = [ { - type: "options", + type: "file", options: { ignore: ["root-ignore"], }, alias: fixture(".babelignore"), - loc: fixture(".babelignore"), dirname: fixture(), }, { - type: "options", + type: "file", options: { plugins: ["json"], }, alias: fixture("json-pkg-config-no-babel", ".babelrc"), - loc: fixture("json-pkg-config-no-babel", ".babelrc"), dirname: fixture("json-pkg-config-no-babel"), }, { @@ -869,7 +854,6 @@ describe("buildConfigChain", function() { filename: fixture("json-pkg-config-no-babel", "src.js"), }, alias: "base", - loc: "base", dirname: base(), }, ]; @@ -885,21 +869,19 @@ describe("buildConfigChain", function() { const expected = [ { - type: "options", + type: "file", options: { ignore: ["root-ignore"], }, alias: fixture(".babelignore"), - loc: fixture(".babelignore"), dirname: fixture(), }, { - type: "options", + type: "file", options: { ignore: ["*", "!src.js"], }, alias: fixture("ignore-negate", ".babelrc"), - loc: fixture("ignore-negate", ".babelrc"), dirname: fixture("ignore-negate"), }, { @@ -908,7 +890,6 @@ describe("buildConfigChain", function() { filename: fixture("ignore-negate", "src.js"), }, alias: "base", - loc: "base", dirname: base(), }, ]; @@ -929,21 +910,19 @@ describe("buildConfigChain", function() { const expected = [ { - type: "options", + type: "file", options: { ignore: ["root-ignore"], }, alias: fixture(".babelignore"), - loc: fixture(".babelignore"), dirname: fixture(), }, { - type: "options", + type: "file", options: { ignore: ["*", "!folder"], }, alias: fixture("ignore-negate-folder", ".babelrc"), - loc: fixture("ignore-negate-folder", ".babelrc"), dirname: fixture("ignore-negate-folder"), }, { @@ -952,7 +931,6 @@ describe("buildConfigChain", function() { filename: fixture("ignore-negate-folder", "folder", "src.js"), }, alias: "base", - loc: "base", dirname: base(), }, ]; diff --git a/packages/babel-core/test/option-manager.js b/packages/babel-core/test/option-manager.js index 7e233a2a67..f6429615f5 100644 --- a/packages/babel-core/test/option-manager.js +++ b/packages/babel-core/test/option-manager.js @@ -17,7 +17,7 @@ describe("option-manager", () => { manageOptions({ randomOption: true, }); - }, /Unknown option: base.randomOption/); + }, /Unknown option: .randomOption/); }); it("throws for removed babel 5 options", () => { @@ -29,7 +29,7 @@ describe("option-manager", () => { }); }, // eslint-disable-next-line max-len - /Using removed Babel 5 option: base.auxiliaryComment - Use `auxiliaryCommentBefore` or `auxiliaryCommentAfter`/, + /Using removed Babel 5 option: .auxiliaryComment - Use `auxiliaryCommentBefore` or `auxiliaryCommentAfter`/, ); }); @@ -47,7 +47,7 @@ describe("option-manager", () => { describe("source type", function() { it("should set module for .mjs extension", () => { const config = manageOptions({ - sourceType: "program", + sourceType: "script", filename: "foo.mjs", }); diff --git a/packages/babel-helper-transform-fixture-test-runner/src/index.js b/packages/babel-helper-transform-fixture-test-runner/src/index.js index 06586688a0..ea6aa1f498 100644 --- a/packages/babel-helper-transform-fixture-test-runner/src/index.js +++ b/packages/babel-helper-transform-fixture-test-runner/src/index.js @@ -259,7 +259,6 @@ export default function( filenameRelative: task.expect.filename, sourceFileName: task.actual.filename, sourceMapTarget: task.expect.filename, - suppressDeprecationMessages: true, babelrc: false, sourceMap: !!(task.sourceMappings || task.sourceMap), }); diff --git a/packages/babel-standalone/README.md b/packages/babel-standalone/README.md index 81681ef825..4743209f7f 100644 --- a/packages/babel-standalone/README.md +++ b/packages/babel-standalone/README.md @@ -18,7 +18,7 @@ Installation There are several ways to get a copy of @babel/standalone. Pick whichever one you like: -- Use it via UNPKG: https://unpkg.com/@babel/standalone@6/babel.min.js. This is a simple way to embed it on a webpage without having to do any other setup. +- Use it via UNPKG: https://unpkg.com/@babel/standalone/babel.min.js. This is a simple way to embed it on a webpage without having to do any other setup. - Install via Bower: `bower install @babel/standalone` - Install via NPM: `npm install --save @babel/standalone` - Manually grab `babel.js` and/or `babel.min.js` from the [GitHub releases page](https://github.com/Daniel15/babel-standalone/releases). Every release includes these files. diff --git a/packages/babylon/src/tokenizer/index.js b/packages/babylon/src/tokenizer/index.js index 8308c9c10b..b16420574a 100644 --- a/packages/babylon/src/tokenizer/index.js +++ b/packages/babylon/src/tokenizer/index.js @@ -437,6 +437,7 @@ export default class Tokenizer extends LocationParser { let type = code === charCodes.asterisk ? tt.star : tt.modulo; let width = 1; let next = this.input.charCodeAt(this.state.pos + 1); + const exprAllowed = this.state.exprAllowed; // Exponentiation operator ** if (code === charCodes.asterisk && next === charCodes.asterisk) { @@ -445,7 +446,7 @@ export default class Tokenizer extends LocationParser { type = tt.exponent; } - if (next === charCodes.equalsTo) { + if (next === charCodes.equalsTo && !exprAllowed) { width++; type = tt.assign; } diff --git a/packages/babylon/test/fixtures/flow/anonymous-function-no-parens-types/good_13/actual.js b/packages/babylon/test/fixtures/flow/anonymous-function-no-parens-types/good_13/actual.js new file mode 100644 index 0000000000..feb53f2b4b --- /dev/null +++ b/packages/babylon/test/fixtures/flow/anonymous-function-no-parens-types/good_13/actual.js @@ -0,0 +1 @@ +const x = ():*=>{} diff --git a/packages/babylon/test/fixtures/flow/anonymous-function-no-parens-types/good_13/expected.json b/packages/babylon/test/fixtures/flow/anonymous-function-no-parens-types/good_13/expected.json new file mode 100644 index 0000000000..ad9ad5c630 --- /dev/null +++ b/packages/babylon/test/fixtures/flow/anonymous-function-no-parens-types/good_13/expected.json @@ -0,0 +1,152 @@ +{ + "type": "File", + "start": 0, + "end": 18, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 18 + } + }, + "program": { + "type": "Program", + "start": 0, + "end": 18, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 18 + } + }, + "sourceType": "module", + "body": [ + { + "type": "VariableDeclaration", + "start": 0, + "end": 18, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 18 + } + }, + "declarations": [ + { + "type": "VariableDeclarator", + "start": 6, + "end": 18, + "loc": { + "start": { + "line": 1, + "column": 6 + }, + "end": { + "line": 1, + "column": 18 + } + }, + "id": { + "type": "Identifier", + "start": 6, + "end": 7, + "loc": { + "start": { + "line": 1, + "column": 6 + }, + "end": { + "line": 1, + "column": 7 + }, + "identifierName": "x" + }, + "name": "x" + }, + "init": { + "type": "ArrowFunctionExpression", + "start": 10, + "end": 18, + "loc": { + "start": { + "line": 1, + "column": 10 + }, + "end": { + "line": 1, + "column": 18 + } + }, + "predicate": null, + "returnType": { + "type": "TypeAnnotation", + "start": 12, + "end": 14, + "loc": { + "start": { + "line": 1, + "column": 12 + }, + "end": { + "line": 1, + "column": 14 + } + }, + "typeAnnotation": { + "type": "ExistsTypeAnnotation", + "start": 13, + "end": 14, + "loc": { + "start": { + "line": 1, + "column": 13 + }, + "end": { + "line": 1, + "column": 14 + } + } + } + }, + "id": null, + "generator": false, + "expression": false, + "async": false, + "params": [], + "body": { + "type": "BlockStatement", + "start": 16, + "end": 18, + "loc": { + "start": { + "line": 1, + "column": 16 + }, + "end": { + "line": 1, + "column": 18 + } + }, + "body": [], + "directives": [] + } + } + } + ], + "kind": "const" + } + ], + "directives": [] + } +} diff --git a/packages/babylon/test/fixtures/flow/classes/good_01/actual.js b/packages/babylon/test/fixtures/flow/classes/good_01/actual.js new file mode 100644 index 0000000000..aabb328bdf --- /dev/null +++ b/packages/babylon/test/fixtures/flow/classes/good_01/actual.js @@ -0,0 +1 @@ +class C { field:*=null } diff --git a/packages/babylon/test/fixtures/flow/classes/good_01/expected.json b/packages/babylon/test/fixtures/flow/classes/good_01/expected.json new file mode 100644 index 0000000000..85cf0ca075 --- /dev/null +++ b/packages/babylon/test/fixtures/flow/classes/good_01/expected.json @@ -0,0 +1,164 @@ +{ + "type": "File", + "start": 0, + "end": 24, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 24 + } + }, + "program": { + "type": "Program", + "start": 0, + "end": 24, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 24 + } + }, + "sourceType": "module", + "body": [ + { + "type": "ClassDeclaration", + "start": 0, + "end": 24, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 24 + } + }, + "id": { + "type": "Identifier", + "start": 6, + "end": 7, + "loc": { + "start": { + "line": 1, + "column": 6 + }, + "end": { + "line": 1, + "column": 7 + }, + "identifierName": "C" + }, + "name": "C" + }, + "superClass": null, + "body": { + "type": "ClassBody", + "start": 8, + "end": 24, + "loc": { + "start": { + "line": 1, + "column": 8 + }, + "end": { + "line": 1, + "column": 24 + } + }, + "body": [ + { + "type": "ClassProperty", + "start": 10, + "end": 22, + "loc": { + "start": { + "line": 1, + "column": 10 + }, + "end": { + "line": 1, + "column": 22 + } + }, + "static": false, + "key": { + "type": "Identifier", + "start": 10, + "end": 15, + "loc": { + "start": { + "line": 1, + "column": 10 + }, + "end": { + "line": 1, + "column": 15 + }, + "identifierName": "field" + }, + "name": "field" + }, + "computed": false, + "variance": null, + "typeAnnotation": { + "type": "TypeAnnotation", + "start": 15, + "end": 17, + "loc": { + "start": { + "line": 1, + "column": 15 + }, + "end": { + "line": 1, + "column": 17 + } + }, + "typeAnnotation": { + "type": "ExistsTypeAnnotation", + "start": 16, + "end": 17, + "loc": { + "start": { + "line": 1, + "column": 16 + }, + "end": { + "line": 1, + "column": 17 + } + } + } + }, + "value": { + "type": "NullLiteral", + "start": 18, + "end": 22, + "loc": { + "start": { + "line": 1, + "column": 18 + }, + "end": { + "line": 1, + "column": 22 + } + } + } + } + ] + } + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/packages/babylon/test/fixtures/flow/classes/good_01/options.json b/packages/babylon/test/fixtures/flow/classes/good_01/options.json new file mode 100644 index 0000000000..65c79d81c7 --- /dev/null +++ b/packages/babylon/test/fixtures/flow/classes/good_01/options.json @@ -0,0 +1,3 @@ +{ + "plugins": ["flow", "classProperties"] +}