Expose a clear API for plugins to override the parser/generator.

This commit is contained in:
Logan Smyth 2017-11-08 16:37:22 -08:00
parent 52d337e4d9
commit fc7fcfac0a
7 changed files with 87 additions and 143 deletions

View File

@ -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`,
);
}

View File

@ -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;

View File

@ -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<BasicDescriptor>,
presets: Array<BasicDescriptor>,
} => {
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.
*/

View File

@ -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<PluginObject, "name">>),
manipulateOptions: (assertFunction: Validator<
@ -21,6 +24,13 @@ const VALIDATORS: ValidatorSet = {
visitor: (assertVisitorMap: Validator<
$PropertyType<PluginObject, "visitor">,
>),
parserOverride: (assertFunction: Validator<
$PropertyType<PluginObject, "parserOverride">,
>),
generatorOverride: (assertFunction: Validator<
$PropertyType<PluginObject, "generatorOverride">,
>),
};
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;
}
}

View File

@ -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

View File

@ -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,
};

View File

@ -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) {