Expose a clear API for plugins to override the parser/generator.
This commit is contained in:
parent
52d337e4d9
commit
fc7fcfac0a
@ -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`,
|
||||
);
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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.
|
||||
*/
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
};
|
||||
|
||||
@ -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) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user