diff --git a/packages/babel-core/src/config/loading/files/index-browser.js b/packages/babel-core/src/config/loading/files/index-browser.js index 2c8f80d79f..1d0b249225 100644 --- a/packages/babel-core/src/config/loading/files/index-browser.js +++ b/packages/babel-core/src/config/loading/files/index-browser.js @@ -42,21 +42,3 @@ export function loadPreset( `Cannot load preset ${name} relative to ${dirname} in a browser`, ); } - -export function loadParser( - name: string, - dirname: string, -): { filepath: string, value: Function } { - throw new Error( - `Cannot load parser ${name} relative to ${dirname} in a browser`, - ); -} - -export function loadGenerator( - name: string, - dirname: string, -): { filepath: string, value: Function } { - throw new Error( - `Cannot load generator ${name} relative to ${dirname} in a browser`, - ); -} diff --git a/packages/babel-core/src/config/loading/files/plugins.js b/packages/babel-core/src/config/loading/files/plugins.js index eed68a4d31..68b369225d 100644 --- a/packages/babel-core/src/config/loading/files/plugins.js +++ b/packages/babel-core/src/config/loading/files/plugins.js @@ -57,62 +57,6 @@ export function loadPreset( return { filepath, value }; } -export function loadParser( - name: string, - dirname: string, -): { filepath: string, value: Function } { - const filepath = resolve.sync(name, { basedir: dirname }); - - const mod = requireModule("parser", filepath); - - if (!mod) { - throw new Error( - `Parser ${name} relative to ${dirname} does not export an object`, - ); - } - if (typeof mod.parse !== "function") { - throw new Error( - `Parser ${name} relative to ${dirname} does not export a .parse function`, - ); - } - const value = mod.parse; - - debug("Loaded parser %o from %o.", name, dirname); - - return { - filepath, - value, - }; -} - -export function loadGenerator( - name: string, - dirname: string, -): { filepath: string, value: Function } { - const filepath = resolve.sync(name, { basedir: dirname }); - - const mod = requireModule("generator", filepath); - - if (!mod) { - throw new Error( - `Generator ${name} relative to ${dirname} does not export an object`, - ); - } - if (typeof mod.print !== "function") { - throw new Error( - `Generator ${name} relative to ${dirname} does not export a .print function`, - ); - } - const value = mod.print; - - debug("Loaded generator %o from %o.", name, dirname); - - return { - filepath, - value, - }; -} - function standardizeName(type: "plugin" | "preset", name: string) { // Let absolute and relative paths through. if (path.isAbsolute(name)) return name; diff --git a/packages/babel-core/src/config/option-manager.js b/packages/babel-core/src/config/option-manager.js index 20cab68f1c..6162b47df9 100644 --- a/packages/babel-core/src/config/option-manager.js +++ b/packages/babel-core/src/config/option-manager.js @@ -12,12 +12,7 @@ import { makeWeakCache } from "./caching"; import { getEnv } from "./helpers/environment"; import { validate, type ValidatedOptions, type PluginItem } from "./options"; -import { - loadPlugin, - loadPreset, - loadParser, - loadGenerator, -} from "./loading/files"; +import { loadPlugin, loadPreset } from "./loading/files"; type MergeOptions = | ConfigItem @@ -196,7 +191,7 @@ const loadConfig = makeWeakCache((config: MergeOptions): { plugins: Array, presets: Array, } => { - const options = normalizeOptions(config); + const options = config.options; const plugins = (config.options.plugins || []).map((plugin, index) => createDescriptor(plugin, loadPlugin, config.dirname, { @@ -321,35 +316,6 @@ const instantiatePreset = makeWeakCache( }, ); -/** - * Validate and return the options object for the config. - */ -function normalizeOptions(config) { - // - const options = Object.assign({}, config.options); - - if (options.parserOpts && typeof options.parserOpts.parser === "string") { - options.parserOpts = Object.assign({}, options.parserOpts); - (options.parserOpts: any).parser = loadParser( - options.parserOpts.parser, - config.dirname, - ).value; - } - - if ( - options.generatorOpts && - typeof options.generatorOpts.generator === "string" - ) { - options.generatorOpts = Object.assign({}, options.generatorOpts); - (options.generatorOpts: any).generator = loadGenerator( - options.generatorOpts.generator, - config.dirname, - ).value; - } - - return options; -} - /** * Given a plugin/preset item, resolve it into a standard format. */ diff --git a/packages/babel-core/src/config/plugin.js b/packages/babel-core/src/config/plugin.js index edc251d0ce..97b1364777 100644 --- a/packages/babel-core/src/config/plugin.js +++ b/packages/babel-core/src/config/plugin.js @@ -8,6 +8,9 @@ import { type Validator, } from "./option-assertions"; +// Note: The casts here are just meant to be static assertions to make sure +// that the assertion functions actually assert that the value's type matches +// the declared types. const VALIDATORS: ValidatorSet = { name: (assertString: Validator<$PropertyType>), manipulateOptions: (assertFunction: Validator< @@ -21,6 +24,13 @@ const VALIDATORS: ValidatorSet = { visitor: (assertVisitorMap: Validator< $PropertyType, >), + + parserOverride: (assertFunction: Validator< + $PropertyType, + >), + generatorOverride: (assertFunction: Validator< + $PropertyType, + >), }; function assertVisitorMap(key: string, value: mixed): VisitorMap { @@ -70,6 +80,9 @@ export type PluginObject = { inherits?: Function, visitor?: VisitorMap, + + parserOverride?: Function, + generatorOverride?: Function, }; export function validatePluginObject(obj: {}): PluginObject { @@ -90,6 +103,9 @@ export default class Plugin { pre: Function | void; visitor: {}; + parserOverride: Function | void; + generatorOverride: Function | void; + options: {}; constructor(plugin: PluginObject, options: {}, key?: string) { @@ -99,6 +115,9 @@ export default class Plugin { this.post = plugin.post; this.pre = plugin.pre; this.visitor = plugin.visitor || {}; + this.parserOverride = plugin.parserOverride; + this.generatorOverride = plugin.generatorOverride; + this.options = options; } } diff --git a/packages/babel-core/src/transformation/file/generate.js b/packages/babel-core/src/transformation/file/generate.js index e31c1726a1..ac8a1e5de7 100644 --- a/packages/babel-core/src/transformation/file/generate.js +++ b/packages/babel-core/src/transformation/file/generate.js @@ -1,5 +1,6 @@ // @flow +import type { PluginPasses } from "../../config"; import convertSourceMap, { type SourceMap } from "convert-source-map"; import sourceMap from "source-map"; import generate from "@babel/generator"; @@ -7,6 +8,7 @@ import generate from "@babel/generator"; import type File from "./file"; export default function generateCode( + pluginPasses: PluginPasses, file: File, ): { outputCode: string, @@ -14,12 +16,33 @@ export default function generateCode( } { const { opts, ast, shebang, code, inputMap } = file; - let gen = generate; - if (opts.generatorOpts && opts.generatorOpts.generator) { - gen = opts.generatorOpts.generator; + const results = []; + for (const plugins of pluginPasses) { + for (const plugin of plugins) { + const { generatorOverride } = plugin; + if (generatorOverride) { + const result = generatorOverride( + ast, + opts.generatorOpts, + code, + generate, + ); + + if (result !== undefined) results.push(result); + } + } } - let { code: outputCode, map: outputMap } = gen(ast, opts.generatorOpts, code); + let result; + if (results.length === 0) { + result = generate(ast, opts.generatorOpts, code); + } else if (results.length === 1) { + result = results[0]; + } else { + throw new Error("More than one plugin attempted to override codegen."); + } + + let { code: outputCode, map: outputMap } = result; if (shebang) { // add back shebang diff --git a/packages/babel-core/src/transformation/index.js b/packages/babel-core/src/transformation/index.js index e72eff1804..ecb2a30b12 100644 --- a/packages/babel-core/src/transformation/index.js +++ b/packages/babel-core/src/transformation/index.js @@ -10,7 +10,7 @@ import normalizeOptions from "./normalize-opts"; import normalizeFile from "./normalize-file"; import generateCode from "./file/generate"; -import File from "./file/file"; +import type File from "./file/file"; export type FileResultCallback = { (Error, null): any, @@ -48,19 +48,24 @@ export function runSync( code: string, ast: ?(BabelNodeFile | BabelNodeProgram), ): FileResult { - const options = normalizeOptions(config); - const input = normalizeFile(options, code, ast); - - const file = new File(options, input); + const file = normalizeFile( + config.passes, + normalizeOptions(config), + code, + ast, + ); transformFile(file, config.passes); - const { outputCode, outputMap } = options.code ? generateCode(file) : {}; + const opts = file.opts; + const { outputCode, outputMap } = opts.code + ? generateCode(config.passes, file) + : {}; return { metadata: file.metadata, - options: options, - ast: options.ast ? file.ast : null, + options: opts, + ast: opts.ast ? file.ast : null, code: outputCode === undefined ? null : outputCode, map: outputMap === undefined ? null : outputMap, }; diff --git a/packages/babel-core/src/transformation/normalize-file.js b/packages/babel-core/src/transformation/normalize-file.js index f264c4eb1f..2b10c05c9e 100644 --- a/packages/babel-core/src/transformation/normalize-file.js +++ b/packages/babel-core/src/transformation/normalize-file.js @@ -1,9 +1,11 @@ // @flow import * as t from "@babel/types"; +import type { PluginPasses } from "../config"; import convertSourceMap, { typeof Converter } from "convert-source-map"; import { parse } from "babylon"; import { codeFrameColumns } from "@babel/code-frame"; +import File from "./file/file"; const shebangRegex = /^#!.*/; @@ -15,10 +17,11 @@ export type NormalizedFile = { }; export default function normalizeFile( + pluginPasses: PluginPasses, options: Object, code: string, ast: ?(BabelNodeFile | BabelNodeProgram), -): NormalizedFile { +): File { code = `${code || ""}`; let shebang = null; @@ -45,35 +48,37 @@ export default function normalizeFile( throw new Error("AST root must be a Program or File node"); } } else { - ast = parser(options, code); + ast = parser(pluginPasses, options, code); } - return { + return new File(options, { code, ast, shebang, inputMap, - }; + }); } -function parser(options, code) { - let parseCode = parse; - - let { parserOpts } = options; - if (parserOpts.parser) { - parseCode = parserOpts.parser; - - parserOpts = Object.assign({}, parserOpts, { - parser: { - parse(source) { - return parse(source, parserOpts); - }, - }, - }); - } - +function parser(pluginPasses, options, code) { try { - return parseCode(code, parserOpts); + const results = []; + for (const plugins of pluginPasses) { + for (const plugin of plugins) { + const { parserOverride } = plugin; + if (parserOverride) { + const ast = parserOverride(code, options.parserOpts, parse); + + if (ast !== undefined) results.push(ast); + } + } + } + + if (results.length === 0) { + return parse(code, options.parserOpts); + } else if (results.length === 1) { + return results[0]; + } + throw new Error("More than one plugin attempted to override parsing."); } catch (err) { const loc = err.loc; if (loc) {