Skip to content

Commit

Permalink
Add @babel/core support for the new assumptions option (#12219, #…
Browse files Browse the repository at this point in the history
  • Loading branch information
nicolo-ribaudo committed Feb 16, 2021
1 parent 795cdc8 commit cdce35e
Show file tree
Hide file tree
Showing 11 changed files with 369 additions and 84 deletions.
32 changes: 32 additions & 0 deletions packages/babel-core/src/config/cache-contexts.js
@@ -0,0 +1,32 @@
// @flow

import type { Targets } from "@babel/helper-compilation-targets";

import type { ConfigContext } from "./config-chain";
import type { CallerMetadata } from "./validation/options";

export type { ConfigContext as FullConfig };

export type FullPreset = {
...ConfigContext,
targets: Targets,
};
export type FullPlugin = {
...FullPreset,
assumptions: { [name: string]: boolean },
};

// Context not including filename since it is used in places that cannot
// process 'ignore'/'only' and other filename-based logic.
export type SimpleConfig = {
envName: string,
caller: CallerMetadata | void,
};
export type SimplePreset = {
...SimpleConfig,
targets: Targets,
};
export type SimplePlugin = {
...SimplePreset,
assumptions: { [name: string]: boolean },
};
128 changes: 64 additions & 64 deletions packages/babel-core/src/config/full.js
Expand Up @@ -14,7 +14,6 @@ import {
type PresetInstance,
} from "./config-chain";
import type { UnloadedDescriptor } from "./config-descriptors";
import type { Targets } from "@babel/helper-compilation-targets";
import traverse from "@babel/traverse";
import {
makeWeakCache,
Expand All @@ -23,28 +22,24 @@ import {
} from "./caching";
import {
validate,
type CallerMetadata,
checkNoUnwrappedItemOptionPairs,
type PluginItem,
} from "./validation/options";
import { validatePluginObject } from "./validation/plugins";
import { makePluginAPI } from "./helpers/config-api";
import { makePluginAPI, makePresetAPI } from "./helpers/config-api";

import loadPrivatePartialConfig from "./partial";
import type { ValidatedOptions } from "./validation/options";

import * as Context from "./cache-contexts";

type LoadedDescriptor = {
value: {},
options: {},
dirname: string,
alias: string,
};

type PluginContext = {
...ConfigContext,
targets: Targets,
};

export type { InputOptions } from "./validation/options";

export type ResolvedConfig = {
Expand All @@ -56,14 +51,6 @@ export type { Plugin };
export type PluginPassList = Array<Plugin>;
export type PluginPasses = Array<PluginPassList>;

// Context not including filename since it is used in places that cannot
// process 'ignore'/'only' and other filename-based logic.
type SimpleContext = {
envName: string,
caller: CallerMetadata | void,
targets: Targets,
};

export default gensync<[any], ResolvedConfig | null>(function* loadFullConfig(
inputOpts: mixed,
): Handler<ResolvedConfig | null> {
Expand All @@ -85,9 +72,10 @@ export default gensync<[any], ResolvedConfig | null>(function* loadFullConfig(
throw new Error("Assertion failure - plugins and presets exist");
}

const pluginContext: PluginContext = {
const pluginContext: Context.FullPlugin = {
...context,
targets: options.targets,
assumptions: options.assumptions ?? {},
};

const toDescriptor = (item: PluginItem) => {
Expand Down Expand Up @@ -229,62 +217,72 @@ function enhanceError<T: Function>(context, fn: T): T {
/**
* Load a generic plugin/preset from the given descriptor loaded from the config object.
*/
const loadDescriptor = makeWeakCache(function* (
{ value, options, dirname, alias }: UnloadedDescriptor,
cache: CacheConfigurator<SimpleContext>,
): Handler<LoadedDescriptor> {
// Disabled presets should already have been filtered out
if (options === false) throw new Error("Assertion failure");

options = options || {};

let item = value;
if (typeof value === "function") {
const factory = maybeAsync(
value,
`You appear to be using an async plugin/preset, but Babel has been called synchronously`,
);
const makeDescriptorLoader = <Context, API>(
apiFactory: (cache: CacheConfigurator<Context>) => API,
): ((d: UnloadedDescriptor, c: Context) => Handler<LoadedDescriptor>) =>
makeWeakCache(function* (
{ value, options, dirname, alias }: UnloadedDescriptor,
cache: CacheConfigurator<Context>,
): Handler<LoadedDescriptor> {
// Disabled presets should already have been filtered out
if (options === false) throw new Error("Assertion failure");

options = options || {};

let item = value;
if (typeof value === "function") {
const factory = maybeAsync(
value,
`You appear to be using an async plugin/preset, but Babel has been called synchronously`,
);

const api = {
...context,
...makePluginAPI(cache),
};
try {
item = yield* factory(api, options, dirname);
} catch (e) {
if (alias) {
e.message += ` (While processing: ${JSON.stringify(alias)})`;
const api = {
...context,
...apiFactory(cache),
};
try {
item = yield* factory(api, options, dirname);
} catch (e) {
if (alias) {
e.message += ` (While processing: ${JSON.stringify(alias)})`;
}
throw e;
}
throw e;
}
}

if (!item || typeof item !== "object") {
throw new Error("Plugin/Preset did not return an object.");
}
if (!item || typeof item !== "object") {
throw new Error("Plugin/Preset did not return an object.");
}

if (isThenable(item)) {
yield* []; // if we want to support async plugins
if (isThenable(item)) {
yield* []; // if we want to support async plugins

throw new Error(
`You appear to be using a promise as a plugin, ` +
`which your current version of Babel does not support. ` +
`If you're using a published plugin, ` +
`you may need to upgrade your @babel/core version. ` +
`As an alternative, you can prefix the promise with "await". ` +
`(While processing: ${JSON.stringify(alias)})`,
);
}
throw new Error(
`You appear to be using a promise as a plugin, ` +
`which your current version of Babel does not support. ` +
`If you're using a published plugin, ` +
`you may need to upgrade your @babel/core version. ` +
`As an alternative, you can prefix the promise with "await". ` +
`(While processing: ${JSON.stringify(alias)})`,
);
}

return { value: item, options, dirname, alias };
});
return { value: item, options, dirname, alias };
});

const pluginDescriptorLoader = makeDescriptorLoader<Context.SimplePlugin, *>(
makePluginAPI,
);
const presetDescriptorLoader = makeDescriptorLoader<Context.SimplePreset, *>(
makePresetAPI,
);

/**
* Instantiate a plugin for the given descriptor, returning the plugin/options pair.
*/
function* loadPluginDescriptor(
descriptor: UnloadedDescriptor,
context: SimpleContext,
context: Context.SimplePlugin,
): Handler<Plugin> {
if (descriptor.value instanceof Plugin) {
if (descriptor.options) {
Expand All @@ -297,14 +295,14 @@ function* loadPluginDescriptor(
}

return yield* instantiatePlugin(
yield* loadDescriptor(descriptor, context),
yield* pluginDescriptorLoader(descriptor, context),
context,
);
}

const instantiatePlugin = makeWeakCache(function* (
{ value, options, dirname, alias }: LoadedDescriptor,
cache: CacheConfigurator<SimpleContext>,
cache: CacheConfigurator<Context.SimplePlugin>,
): Handler<Plugin> {
const pluginObj = validatePluginObject(value);

Expand Down Expand Up @@ -387,9 +385,11 @@ const validatePreset = (
*/
function* loadPresetDescriptor(
descriptor: UnloadedDescriptor,
context: PluginContext,
context: Context.FullPreset,
): Handler<ConfigChain | null> {
const preset = instantiatePreset(yield* loadDescriptor(descriptor, context));
const preset = instantiatePreset(
yield* presetDescriptorLoader(descriptor, context),
);
validatePreset(preset, context, descriptor);
return yield* buildPresetChain(preset, context);
}
Expand Down
35 changes: 24 additions & 11 deletions packages/babel-core/src/config/helpers/config-api.js
Expand Up @@ -13,6 +13,8 @@ import {

import type { CallerMetadata } from "../validation/options";

import * as Context from "../cache-contexts";

type EnvFunction = {
(): string,
<T>((string) => T): T,
Expand All @@ -24,6 +26,8 @@ type CallerFactory = ((CallerMetadata | void) => mixed) => SimpleType;

type TargetsFunction = () => Targets;

type AssumptionFunction = (name: string) => boolean | void;

export type ConfigAPI = {|
version: string,
cache: SimpleCacheConfigurator,
Expand All @@ -33,14 +37,19 @@ export type ConfigAPI = {|
caller?: CallerFactory,
|};

export type PluginAPI = {|
export type PresetAPI = {|
...ConfigAPI,
targets: TargetsFunction,
|};

export function makeConfigAPI<
SideChannel: { envName: string, caller: CallerMetadata | void },
>(cache: CacheConfigurator<SideChannel>): ConfigAPI {
export type PluginAPI = {|
...PresetAPI,
assumption: AssumptionFunction,
|};

export function makeConfigAPI<SideChannel: Context.SimpleConfig>(
cache: CacheConfigurator<SideChannel>,
): ConfigAPI {
const env: any = value =>
cache.using(data => {
if (typeof value === "undefined") return data.envName;
Expand Down Expand Up @@ -70,13 +79,9 @@ export function makeConfigAPI<
};
}

export function makePluginAPI(
cache: CacheConfigurator<{
envName: string,
caller: CallerMetadata | void,
targets: Targets,
}>,
): PluginAPI {
export function makePresetAPI<SideChannel: Context.SimplePreset>(
cache: CacheConfigurator<SideChannel>,
): PresetAPI {
const targets = () =>
// We are using JSON.parse/JSON.stringify because it's only possible to cache
// primitive values. We can safely stringify the targets object because it
Expand All @@ -86,6 +91,14 @@ export function makePluginAPI(
return { ...makeConfigAPI(cache), targets };
}

export function makePluginAPI<SideChannel: Context.SimplePlugin>(
cache: CacheConfigurator<SideChannel>,
): PluginAPI {
const assumption = name => cache.using(data => data.assumptions[name]);

return { ...makePresetAPI(cache), assumption };
}

function assertVersion(range: string | number): void {
if (typeof range === "number") {
if (!Number.isInteger(range)) {
Expand Down
4 changes: 3 additions & 1 deletion packages/babel-core/src/config/partial.js
Expand Up @@ -117,7 +117,9 @@ export default function* loadPrivatePartialConfig(
const configChain = yield* buildRootChain(args, context);
if (!configChain) return null;

const merged: ValidatedOptions = {};
const merged: ValidatedOptions = {
assumptions: {},
};
configChain.options.forEach(opts => {
mergeOptions((merged: any), opts);
});
Expand Down
13 changes: 6 additions & 7 deletions packages/babel-core/src/config/util.js
Expand Up @@ -7,14 +7,13 @@ export function mergeOptions(
source: ValidatedOptions | NormalizedOptions,
): void {
for (const k of Object.keys(source)) {
if (k === "parserOpts" && source.parserOpts) {
const parserOpts = source.parserOpts;
const targetObj = (target.parserOpts = target.parserOpts || {});
if (
(k === "parserOpts" || k === "generatorOpts" || k === "assumptions") &&
source[k]
) {
const parserOpts = source[k];
const targetObj = target[k] || (target[k] = {});
mergeDefaultFields(targetObj, parserOpts);
} else if (k === "generatorOpts" && source.generatorOpts) {
const generatorOpts = source.generatorOpts;
const targetObj = (target.generatorOpts = target.generatorOpts || {});
mergeDefaultFields(targetObj, generatorOpts);
} else {
const val = source[k];
if (val !== undefined) target[k] = (val: any);
Expand Down
36 changes: 36 additions & 0 deletions packages/babel-core/src/config/validation/option-assertions.js
Expand Up @@ -24,6 +24,8 @@ import type {
TargetsListOrObject,
} from "./options";

import { assumptionsNames } from "./options";

export type { RootPath } from "./options";

export type ValidatorSet = {
Expand Down Expand Up @@ -431,3 +433,37 @@ function assertBrowserVersion(loc: GeneralPath, value: mixed) {

throw new Error(`${msg(loc)} must be a string or an integer number`);
}

export function assertAssumptions(
loc: GeneralPath,
value: mixed,
): { [name: string]: boolean } | void {
if (value === undefined) return;

if (typeof value !== "object" || value === null) {
throw new Error(`${msg(loc)} must be an object or undefined.`);
}

let root = loc;
do {
root = root.parent;
} while (root.type !== "root");
const inPreset = root.source === "preset";

for (const name of Object.keys(value)) {
const subLoc = access(loc, name);
if (!assumptionsNames.has(name)) {
throw new Error(`${msg(subLoc)} is not a supported assumption.`);
}
if (typeof value[name] !== "boolean") {
throw new Error(`${msg(subLoc)} must be a boolean.`);
}
if (inPreset && value[name] === false) {
throw new Error(
`${msg(subLoc)} cannot be set to 'false' inside presets.`,
);
}
}

return (value: any);
}

0 comments on commit cdce35e

Please sign in to comment.