From 74ab2798e2a68e6a081ea783ecb1e97d947e68af Mon Sep 17 00:00:00 2001 From: Logan Smyth Date: Wed, 7 Mar 2018 18:02:38 -0800 Subject: [PATCH] Tweaks around PR comments. --- packages/babel-core/README.md | 5 +- packages/babel-core/src/config/item.js | 124 ++++++++++++---------- packages/babel-core/src/config/partial.js | 38 ++++--- 3 files changed, 93 insertions(+), 74 deletions(-) diff --git a/packages/babel-core/README.md b/packages/babel-core/README.md index 810f84238e..c2e3fd3436 100644 --- a/packages/babel-core/README.md +++ b/packages/babel-core/README.md @@ -93,7 +93,7 @@ babel.transformFileSync("filename.js", options).code; ``` -## babel.transformFromAst(ast: Object, code?: string, [options?](#options): Object, callback: Function) +## babel.transformFromAst(ast: Object, code?: string, [options?](#options): Object, callback: Function): FileNode | null Given an [AST](https://astexplorer.net/), transform it. @@ -167,6 +167,7 @@ and pass it back to Babel again. * It can be safely passed back to Babel. Fields like `babelrc` have been set to false so that later calls to Babel will not make a second attempt to load config files. +* `hasFilesystemConfig(): boolean` - Check if the resolved config loaded any settings from the filesystem. [`ConfigItem`](#configitem-type) instances expose properties to introspect the values, but each item should be treated as immutable. If changes are desired, the item should be @@ -175,7 +176,7 @@ with a replacement item created by `babel.createConfigItem`. See that function for information about `ConfigItem` fields. -### babel.createConfigItem(value: string | {} | Function, options?: {}, { dirname?: string, name?: string, type?: "preset" | "plugin" }): ConfigItem +### babel.createConfigItem(value: string | {} | Function | [string | {} | Function, {} | void], { dirname?: string, type?: "preset" | "plugin" }): ConfigItem Allows build tooling to create and cache config items up front. If this function is called multiple times for a given plugin, Babel will call the plugin's function itself diff --git a/packages/babel-core/src/config/item.js b/packages/babel-core/src/config/item.js index 78fcc50926..ab9b51207e 100644 --- a/packages/babel-core/src/config/item.js +++ b/packages/babel-core/src/config/item.js @@ -1,5 +1,7 @@ // @flow +import type { PluginTarget, PluginOptions } from "./validation/options"; + import path from "path"; import { createDescriptor, @@ -10,27 +12,29 @@ export function createItemFromDescriptor(desc: UnloadedDescriptor): ConfigItem { return new ConfigItem(desc); } +/** + * Create a config item using the same value format used in Babel's config + * files. Items returned from this function should be cached by the caller + * ideally, as recreating the config item will mean re-resolving the item + * and re-evaluating the plugin/preset function. + */ export function createConfigItem( - value: string | {} | Function, - options?: {} | void, + value: + | PluginTarget + | [PluginTarget, PluginOptions] + | [PluginTarget, PluginOptions, string | void], { dirname = ".", - name, type, }: { dirname?: string, - name?: string, type?: "preset" | "plugin", } = {}, ): ConfigItem { - const descriptor = createDescriptor( - [value, options, name], - path.resolve(dirname), - { - type, - alias: "programmatic item", - }, - ); + const descriptor = createDescriptor(value, path.resolve(dirname), { + type, + alias: "programmatic item", + }); return createItemFromDescriptor(descriptor); } @@ -43,78 +47,82 @@ export function getItemDescriptor(item: mixed): UnloadedDescriptor | void { return undefined; } +export type { ConfigItem }; + /** * A public representation of a plugin/preset that will _eventually_ be load. * Users can use this to interact with the results of a loaded Babel * configuration. + * + * Any changes to public properties of this class should be considered a + * breaking change to Babel's API. */ -export class ConfigItem { +class ConfigItem { + /** + * The private underlying descriptor that Babel actually cares about. + * If you access this, you are a bad person. + */ _descriptor: UnloadedDescriptor; - constructor(descriptor: UnloadedDescriptor) { - this._descriptor = descriptor; - - // Make people less likely to stumble onto this if they are exploring - // programmatically. - enumerable(this, "_descriptor", false); - } - /** * The resolved value of the item itself. */ - get value(): {} | Function { - return this._descriptor.value; - } + value: {} | Function; /** * The options, if any, that were passed to the item. * Mutating this will lead to undefined behavior. If you need */ - get options(): {} | void { - const options = this._descriptor.options; - if (options === false) { - throw new Error("Assertion failure - unexpected false options"); - } - - return options; - } + options: {} | void; /** * The directory that the options for this item are relative to. */ - get dirname(): string { - return this._descriptor.dirname; - } + dirname: string; /** * Get the name of the plugin, if the user gave it one. */ - get name(): string | void { - return this._descriptor.name; - } + name: string | void; - get file(): { + /** + * Data about the file that the item was loaded from, if Babel knows it. + */ + file: { + // The requested path, e.g. "@babel/env". request: string, - resolved: string, - } | void { - const file = this._descriptor.file; - if (!file) return undefined; - return { - request: file.request, - resolved: file.resolved, - }; + // The resolved absolute path of the file. + resolved: string, + } | void; + + constructor(descriptor: UnloadedDescriptor) { + // Make people less likely to stumble onto this if they are exploring + // programmatically, and also make sure that if people happen to + // pass the item through JSON.stringify, it doesn't show up. + this._descriptor = descriptor; + Object.defineProperty(this, "_descriptor", ({ enumerable: false }: any)); + + if (this._descriptor.options === false) { + throw new Error("Assertion failure - unexpected false options"); + } + + this.value = this._descriptor.value; + this.options = this._descriptor.options; + this.dirname = this._descriptor.dirname; + this.name = this._descriptor.name; + this.file = this._descriptor.file + ? { + request: this._descriptor.file.request, + resolved: this._descriptor.file.resolved, + } + : undefined; + + // Freeze the object to make it clear that people shouldn't expect mutating + // this object to do anything. A new item should be created if they want + // to change something. + Object.freeze(this); } } -// Make these slightly easier for people to find if they are exploring the -// API programmatically. -enumerable(ConfigItem.prototype, "value", true); -enumerable(ConfigItem.prototype, "options", true); -enumerable(ConfigItem.prototype, "dirname", true); -enumerable(ConfigItem.prototype, "name", true); -enumerable(ConfigItem.prototype, "file", true); - -function enumerable(obj: {}, prop: string, enumerable: boolean) { - Object.defineProperty(obj, prop, { enumerable }); -} +Object.freeze(ConfigItem.prototype); diff --git a/packages/babel-core/src/config/partial.js b/packages/babel-core/src/config/partial.js index 877e924075..83d0d08086 100644 --- a/packages/babel-core/src/config/partial.js +++ b/packages/babel-core/src/config/partial.js @@ -92,27 +92,37 @@ export function loadPartialConfig(inputOpts: mixed): PartialConfig | null { export type { PartialConfig }; class PartialConfig { - _options: ValidatedOptions; - _babelrc: string | void; - _babelignore: string | void; + /** + * These properties are public, so any changes to them should be considered + * a breaking change to Babel's API. + */ + options: ValidatedOptions; + babelrc: string | void; + babelignore: string | void; constructor( options: ValidatedOptions, babelrc: string | void, ignore: string | void, ) { - this._options = options; - this._babelignore = ignore; - this._babelrc = babelrc; + this.options = options; + this.babelignore = ignore; + this.babelrc = babelrc; + + // Freeze since this is a public API and it should be extremely obvious that + // reassigning properties on here does nothing. + Object.freeze(this); } - get babelignore(): string | void { - return this._babelignore; - } - get babelrc(): string | void { - return this._babelrc; - } - get options(): ValidatedOptions { - return this._options; + /** + * Returns true if their is a config file in the filesystem for this config. + * + * While this only means .babelrc(.mjs)?/package.json#babel right now, it + * may well expand in the future, so using this is recommended vs checking + * this.babelrc directly. + */ + hasFilesystemConfig(): boolean { + return this.babelrc !== undefined; } } +Object.freeze(PartialConfig.prototype);