Merge pull request #5463 from loganfsmyth/strict-args

More strictly parse configs and explicitly handle arguments in babel-cli
This commit is contained in:
Logan Smyth
2017-03-14 10:21:32 -07:00
committed by GitHub
14 changed files with 195 additions and 385 deletions

View File

@@ -1,3 +1,4 @@
import defaults from "lodash/defaults";
import outputFileSync from "output-file-sync";
import slash from "slash";
import path from "path";
@@ -5,17 +6,17 @@ import fs from "fs";
import * as util from "./util";
export default function (commander, filenames) {
export default function (commander, filenames, opts) {
function write(src, relative) {
// remove extension and then append back on .js
relative = relative.replace(/\.(\w*?)$/, "") + ".js";
const dest = path.join(commander.outDir, relative);
const data = util.compile(src, {
const data = util.compile(src, defaults({
sourceFileName: slash(path.relative(dest + "/..", src)),
sourceMapTarget: path.basename(relative),
});
}, opts));
if (!commander.copyFiles && data.ignored) return;
// we've requested explicit sourcemaps to be written to disk
@@ -32,7 +33,7 @@ export default function (commander, filenames) {
}
function handleFile(src, filename) {
if (util.shouldIgnore(src)) return;
if (util.shouldIgnore(src, opts)) return;
if (util.canCompile(filename, commander.extensions)) {
write(src, filename);

View File

@@ -1,4 +1,5 @@
import convertSourceMap from "convert-source-map";
import defaults from "lodash/defaults";
import sourceMap from "source-map";
import slash from "slash";
import path from "path";
@@ -96,9 +97,9 @@ export default function (commander, filenames, opts) {
});
process.stdin.on("end", function () {
results.push(util.transform(commander.filename, code, {
results.push(util.transform(commander.filename, code, defaults({
sourceFileName: "stdin",
}));
}, opts)));
output();
});
};
@@ -123,7 +124,7 @@ export default function (commander, filenames, opts) {
});
_filenames.forEach(function (filename) {
if (util.shouldIgnore(filename)) return;
if (util.shouldIgnore(filename, opts)) return;
let sourceFilename = filename;
if (commander.outFile) {
@@ -131,9 +132,9 @@ export default function (commander, filenames, opts) {
}
sourceFilename = slash(sourceFilename);
const data = util.compile(filename, {
const data = util.compile(filename, defaults({
sourceFileName: sourceFilename,
});
}, opts));
if (data.ignored) return;
results.push(data);
@@ -158,7 +159,7 @@ export default function (commander, filenames, opts) {
pollInterval: 10,
},
}).on("all", function (type, filename) {
if (util.shouldIgnore(filename) || !util.canCompile(filename, commander.extensions)) return;
if (util.shouldIgnore(filename, opts) || !util.canCompile(filename, commander.extensions)) return;
if (type === "add" || type === "change") {
util.log(type + " " + filename);

View File

@@ -2,8 +2,7 @@
import fs from "fs";
import commander from "commander";
import kebabCase from "lodash/kebabCase";
import { options, util, version } from "babel-core";
import { util, version } from "babel-core";
import uniq from "lodash/uniq";
import glob from "glob";
@@ -12,34 +11,41 @@ import fileCommand from "./file";
import pkg from "../../package.json";
Object.keys(options).forEach(function (key) {
const option = options[key];
if (option.hidden) return;
let arg = kebabCase(key);
if (option.type !== "boolean") {
arg += " [" + (option.type || "string") + "]";
}
if (option.type === "boolean" && option.default === true) {
arg = "no-" + arg;
}
arg = "--" + arg;
if (option.shorthand) {
arg = "-" + option.shorthand + ", " + arg;
}
const desc = [];
if (option.deprecated) desc.push("[DEPRECATED] " + option.deprecated);
if (option.description) desc.push(option.description);
commander.option(arg, desc.join(" "));
});
/* eslint-disable max-len */
// Standard Babel input configs.
commander.option("-f, --filename [filename]", "filename to use when reading from stdin - this will be used in source-maps, errors etc");
commander.option("--presets [list]", "comma-separated list of preset names");
commander.option("--plugins [list]", "comma-separated list of plugin names");
// Basic file input configuration.
commander.option("--source-type [script|module]", "");
commander.option("--no-babelrc", "Whether or not to look up .babelrc and .babelignore files");
commander.option("--ignore [list]", "list of glob paths to **not** compile");
commander.option("--only [list]", "list of glob paths to **only** compile");
// Misc babel config.
commander.option("--no-highlight-code", "enable/disable ANSI syntax highlighting of code frames (on by default)");
// General output formatting.
commander.option("--no-comments", "write comments to generated output (true by default)");
commander.option("--retain-lines", "retain line numbers - will result in really ugly code");
commander.option("--compact [true|false|auto]", "do not include superfluous whitespace characters and line terminators");
commander.option("--minified", "save as much bytes when printing [true|false]");
commander.option("--auxiliary-comment-before [string]", "print a comment before any injected non-user code");
commander.option("--auxiliary-comment-after [string]", "print a comment after any injected non-user code");
// General soucemap formatting.
commander.option("-s, --source-maps [true|false|inline|both]", "");
commander.option("--source-map-target [string]", "set `file` on returned source map");
commander.option("--source-file-name [string]", "set `sources[0]` on returned source map");
commander.option("--source-root [filename]", "the root from which all sources are relative");
// Config params for certain module output formats.
commander.option("--module-root [filename]", "optional prefix for the AMD module formatter that will be prepend to the filename on module definitions");
commander.option("-M, --module-ids", "insert an explicit id for modules");
commander.option("--module-id [string]", "specify a custom name for module ids");
// "babel" command specific arguments that are not passed to babel-core.
commander.option("-x, --extensions [extensions]", "List of extensions to compile when a directory has been input [.es6,.js,.es,.jsx]");
commander.option("-w, --watch", "Recompile files on changes");
commander.option("--skip-initial-build", "Do not compile files before watching");
@@ -106,14 +112,23 @@ if (errors.length) {
//
export const opts = {};
const opts = commander.opts();
Object.keys(options).forEach(function (key) {
const opt = options[key];
if (commander[key] !== undefined && commander[key] !== opt.default) {
opts[key] = commander[key];
}
});
// Delete options that are specific to babel-cli and shouldn't be passed to babel-core.
delete opts.version;
delete opts.extensions;
delete opts.watch;
delete opts.skipInitialBuild;
delete opts.outFile;
delete opts.outDir;
delete opts.copyFiles;
delete opts.quiet;
// Commander will default the "--no-" arguments to true, but we want to leave them undefined so that
// babel-core can handle the default-assignment logic on its own.
if (opts.babelrc === true) opts.babelrc = undefined;
if (opts.comments === true) opts.comments = undefined;
if (opts.highlightCode === true) opts.highlightCode = undefined;
opts.ignore = util.arrayify(opts.ignore, util.regexify);
@@ -121,5 +136,10 @@ if (opts.only) {
opts.only = util.arrayify(opts.only, util.regexify);
}
if (opts.sourceMaps) opts.sourceMaps = util.booleanify(opts.sourceMaps);
if (opts.compact) opts.compact = util.booleanify(opts.compact);
if (opts.presets) opts.presets = util.list(opts.presets);
if (opts.plugins) opts.plugins = util.list(opts.plugins);
const fn = commander.outDir ? dirCommand : fileCommand;
fn(commander, filenames, opts);

View File

@@ -1,12 +1,9 @@
import commander from "commander";
import defaults from "lodash/defaults";
import readdir from "fs-readdir-recursive";
import * as babel from "babel-core";
import path from "path";
import fs from "fs";
import * as index from "./index";
export function chmod(src, dest) {
fs.chmodSync(dest, fs.statSync(src).mode);
}
@@ -21,8 +18,8 @@ export { readdir };
export const canCompile = babel.util.canCompile;
export function shouldIgnore(loc) {
return babel.util.shouldIgnore(loc, index.opts.ignore, index.opts.only);
export function shouldIgnore(loc, opts) {
return babel.util.shouldIgnore(loc, opts.ignore, opts.only);
}
export function addSourceMappingUrl(code, loc) {
@@ -34,8 +31,9 @@ export function log(msg) {
}
export function transform(filename, code, opts) {
opts = defaults(opts || {}, index.opts);
opts.filename = filename;
opts = Object.assign({}, opts, {
filename,
});
const result = babel.transform(code, opts);
result.filename = filename;

View File

@@ -1,7 +1,6 @@
import fs from "fs";
export { default as File } from "./transformation/file";
export { default as options } from "./transformation/file/options/config";
export { default as buildExternalHelpers } from "./tools/build-external-helpers";
export { default as template } from "babel-template";
export { default as resolvePlugin } from "./helpers/resolve-plugin";

View File

@@ -143,10 +143,6 @@ export default class File extends Store {
opts.basename = path.basename(opts.filename, path.extname(opts.filename));
opts.ignore = util.arrayify(opts.ignore, util.regexify);
if (opts.only) opts.only = util.arrayify(opts.only, util.regexify);
defaults(opts, {
moduleRoot: opts.sourceRoot,
});
@@ -577,7 +573,7 @@ export default class File extends Store {
if (!opts.code) return this.makeResult(result);
let gen = generate;
if (opts.generatorOpts.generator) {
if (opts.generatorOpts && opts.generatorOpts.generator) {
gen = opts.generatorOpts.generator;
if (typeof gen === "string") {

View File

@@ -1,213 +0,0 @@
/* eslint max-len: "off" */
export default {
filename: {
type: "filename",
description: "filename to use when reading from stdin - this will be used in source-maps, errors etc",
default: "unknown",
shorthand: "f",
},
filenameRelative: {
hidden: true,
type: "string",
},
inputSourceMap: {
hidden: true,
},
env: {
hidden: true,
default: {},
},
mode: {
description: "",
hidden: true,
},
retainLines: {
type: "boolean",
default: false,
description: "retain line numbers - will result in really ugly code",
},
highlightCode: {
description: "enable/disable ANSI syntax highlighting of code frames (on by default)",
type: "boolean",
default: true,
},
suppressDeprecationMessages: {
type: "boolean",
default: false,
hidden: true,
},
presets: {
type: "list",
description: "",
default: [],
},
plugins: {
type: "list",
default: [],
description: "",
},
ignore: {
type: "list",
description: "list of glob paths to **not** compile",
default: [],
},
only: {
type: "list",
description: "list of glob paths to **only** compile",
},
code: {
hidden: true,
default: true,
type: "boolean",
},
metadata: {
hidden: true,
default: true,
type: "boolean",
},
ast: {
hidden: true,
default: true,
type: "boolean",
},
extends: {
type: "string",
hidden: true,
},
comments: {
type: "boolean",
default: true,
description: "write comments to generated output (true by default)",
},
shouldPrintComment: {
hidden: true,
description: "optional callback to control whether a comment should be inserted, when this is used the comments option is ignored",
},
wrapPluginVisitorMethod: {
hidden: true,
description: "optional callback to wrap all visitor methods",
},
compact: {
type: "booleanString",
default: "auto",
description: "do not include superfluous whitespace characters and line terminators [true|false|auto]",
},
minified: {
type: "boolean",
default: false,
description: "save as much bytes when printing [true|false]",
},
sourceMap: {
alias: "sourceMaps",
hidden: true,
},
sourceMaps: {
type: "booleanString",
description: "[true|false|inline]",
default: false,
shorthand: "s",
},
sourceMapTarget: {
type: "string",
description: "set `file` on returned source map",
},
sourceFileName: {
type: "string",
description: "set `sources[0]` on returned source map",
},
sourceRoot: {
type: "filename",
description: "the root from which all sources are relative",
},
babelrc: {
description: "Whether or not to look up .babelrc and .babelignore files",
type: "boolean",
default: true,
},
sourceType: {
description: "",
default: "module",
},
auxiliaryCommentBefore: {
type: "string",
description: "print a comment before any injected non-user code",
},
auxiliaryCommentAfter: {
type: "string",
description: "print a comment after any injected non-user code",
},
resolveModuleSource: {
hidden: true,
},
getModuleId: {
hidden: true,
},
moduleRoot: {
type: "filename",
description: "optional prefix for the AMD module formatter that will be prepend to the filename on module definitions",
},
moduleIds: {
type: "boolean",
default: false,
shorthand: "M",
description: "insert an explicit id for modules",
},
moduleId: {
description: "specify a custom name for module ids",
type: "string",
},
passPerPreset: {
description: "Whether to spawn a traversal pass per a preset. By default all presets are merged.",
type: "boolean",
default: false,
hidden: true,
},
// Deprecate top level parserOpts
parserOpts: {
description: "Options to pass into the parser, or to change parsers (parserOpts.parser)",
default: false,
},
// Deprecate top level generatorOpts
generatorOpts: {
description: "Options to pass into the generator, or to change generators (generatorOpts.generator)",
default: false,
},
};

View File

@@ -1,22 +0,0 @@
import * as parsers from "./parsers";
import config from "./config";
export { config };
export function normaliseOptions(options: Object = {}): Object {
for (const key in options) {
let val = options[key];
if (val == null) continue;
let opt = config[key];
if (opt && opt.alias) opt = config[opt.alias];
if (!opt) continue;
const parser = parsers[opt.type];
if (parser) val = parser(val);
options[key] = val;
}
return options;
}

View File

@@ -1,16 +1,14 @@
import * as context from "../../../index";
import Plugin from "../../plugin";
import * as messages from "babel-messages";
import { normaliseOptions } from "./index";
import resolvePlugin from "../../../helpers/resolve-plugin";
import resolvePreset from "../../../helpers/resolve-preset";
import cloneDeepWith from "lodash/cloneDeepWith";
import clone from "lodash/clone";
import merge from "../../../helpers/merge";
import config from "./config";
import removed from "./removed";
import buildConfigChain from "./build-config-chain";
import path from "path";
import * as util from "../../../util";
type PluginObject = {
pre?: Function;
@@ -33,6 +31,48 @@ type MergeOptions = {
dirname?: string
};
const optionNames = new Set([
"filename",
"filenameRelative",
"inputSourceMap",
"env",
"mode",
"retainLines",
"highlightCode",
"suppressDeprecationMessages",
"presets",
"plugins",
"ignore",
"only",
"code",
"metadata",
"ast",
"extends",
"comments",
"shouldPrintComment",
"wrapPluginVisitorMethod",
"compact",
"minified",
"sourceMaps",
"sourceMapTarget",
"sourceFileName",
"sourceRoot",
"babelrc",
"sourceType",
"auxiliaryCommentBefore",
"auxiliaryCommentAfter",
"resolveModuleSource",
"getModuleId",
"moduleRoot",
"moduleIds",
"moduleId",
"passPerPreset",
// Deprecate top level parserOpts
"parserOpts",
// Deprecate top level generatorOpts
"generatorOpts",
]);
export default class OptionManager {
constructor() {
this.resolvedConfigs = [];
@@ -73,14 +113,17 @@ export default class OptionManager {
}
static createBareOptions() {
const opts = {};
for (const key in config) {
const opt = config[key];
opts[key] = clone(opt.default);
}
return opts;
return {
sourceType: "module",
babelrc: true,
filename: "unknown",
code: true,
metadata: true,
ast: true,
comments: true,
compact: "auto",
highlightCode: true,
};
}
static normalisePlugin(plugin, loc, i, alias) {
@@ -169,11 +212,18 @@ export default class OptionManager {
dirname = dirname || process.cwd();
loc = loc || alias;
for (const key in opts) {
const option = config[key];
if (opts.sourceMap !== undefined) {
if (opts.sourceMaps !== undefined) {
throw new Error(`Both ${alias}.sourceMap and .sourceMaps have been set`);
}
opts.sourceMaps = opts.sourceMap;
delete opts.sourceMap;
}
for (const key in opts) {
// check for an unknown option
if (!option) {
if (!optionNames.has(key)) {
if (removed[key]) {
throw new ReferenceError(`Using removed Babel 5 option: ${alias}.${key} - ${removed[key].message}`);
} else {
@@ -185,16 +235,28 @@ export default class OptionManager {
}
}
// normalise options
normaliseOptions(opts);
if (opts.ignore) {
if (!Array.isArray(rawOpts.ignore)) throw new Error(`${alias}.ignore should be an array`);
opts.ignore = opts.ignore.map(util.regexify);
}
if (opts.only) {
if (!Array.isArray(rawOpts.only)) throw new Error(`${alias}.only should be an array`);
opts.only = opts.only.map(util.regexify);
}
// resolve plugins
if (opts.plugins) {
if (!Array.isArray(rawOpts.plugins)) throw new Error(`${alias}.plugins should be an array`);
opts.plugins = OptionManager.normalisePlugins(loc, dirname, opts.plugins);
}
// resolve presets
if (opts.presets) {
if (!Array.isArray(rawOpts.presets)) throw new Error(`${alias}.presets should be an array`);
// If we're in the "pass per preset" mode, we resolve the presets
// and keep them for further execution to calculate the options.
if (opts.passPerPreset) {
@@ -309,33 +371,11 @@ export default class OptionManager {
return presetFactory(context, options, meta);
}
normaliseOptions() {
const opts = this.options;
for (const key in config) {
const option = config[key];
const val = opts[key];
// optional
if (!val && option.optional) continue;
// aliases
if (option.alias) {
opts[option.alias] = opts[option.alias] || val;
} else {
opts[key] = val;
}
}
}
init(opts: Object = {}): Object {
for (const config of buildConfigChain(opts)) {
this.mergeOptions(config);
}
// normalise
this.normaliseOptions(opts);
return this.options;
}
}

View File

@@ -1,16 +0,0 @@
import slash from "slash";
import * as util from "../../../util";
export const filename = slash;
export function boolean(val: any): boolean {
return !!val;
}
export function booleanString(val: any): boolean | any {
return util.booleanify(val);
}
export function list(val: any): Array<string> {
return util.list(val);
}

View File

@@ -496,17 +496,17 @@ describe("api", function () {
it("ignore option", function () {
return Promise.all([
transformAsync("", {
ignore: "node_modules",
ignore: ["node_modules"],
filename: "/foo/node_modules/bar",
}).then(assertIgnored),
transformAsync("", {
ignore: "foo/node_modules",
ignore: ["foo/node_modules"],
filename: "/foo/node_modules/bar",
}).then(assertIgnored),
transformAsync("", {
ignore: "foo/node_modules/*.bar",
ignore: ["foo/node_modules/*.bar"],
filename: "/foo/node_modules/foo.bar",
}).then(assertIgnored),
]);
@@ -515,32 +515,32 @@ describe("api", function () {
it("only option", function () {
return Promise.all([
transformAsync("", {
only: "node_modules",
only: ["node_modules"],
filename: "/foo/node_modules/bar",
}).then(assertNotIgnored),
transformAsync("", {
only: "foo/node_modules",
only: ["foo/node_modules"],
filename: "/foo/node_modules/bar",
}).then(assertNotIgnored),
transformAsync("", {
only: "foo/node_modules/*.bar",
only: ["foo/node_modules/*.bar"],
filename: "/foo/node_modules/foo.bar",
}).then(assertNotIgnored),
transformAsync("", {
only: "node_modules",
only: ["node_modules"],
filename: "/foo/node_module/bar",
}).then(assertIgnored),
transformAsync("", {
only: "foo/node_modules",
only: ["foo/node_modules"],
filename: "/bar/node_modules/foo",
}).then(assertIgnored),
transformAsync("", {
only: "foo/node_modules/*.bar",
only: ["foo/node_modules/*.bar"],
filename: "/foo/node_modules/bar.foo",
}).then(assertIgnored),
]);

View File

@@ -44,14 +44,16 @@ var code = (function() {
}).toString().split('\n').slice(1, -1).join('\n');
transform(code, {
plugins: function (b) {
var t = b.types;
return {
visitor: {
BinaryExpression: function(path) {
path.get("left").baseTypeStrictlyMatches(path.get("right"));
plugins: [
function (b) {
var t = b.types;
return {
visitor: {
BinaryExpression: function(path) {
path.get("left").baseTypeStrictlyMatches(path.get("right"));
}
}
}
}
}
]
});

View File

@@ -5,14 +5,16 @@ var code = multiline([
]);
transform(code, {
plugins: function (b) {
var t = b.types;
return {
visitor: {
ConditionalExpression: function(path) {
path.get("test").evaluateTruthy();
plugins: [
function (b) {
var t = b.types;
return {
visitor: {
ConditionalExpression: function(path) {
path.get("test").evaluateTruthy();
}
}
}
}
}
},
],
});

View File

@@ -1,19 +1,21 @@
var res = transform('', {
plugins: function (b) {
var t = b.types;
return {
visitor: {
Program: function(path) {
if (this.done) return;
// if (false) { if (true) 42; } else 23;
var inner = t.ifStatement(t.booleanLiteral(true), t.expressionStatement(t.numericLiteral(42)), null);
var outer = t.ifStatement(t.booleanLiteral(false), inner, t.expressionStatement(t.numericLiteral(23)));
path.replaceWith(t.program([outer]));
this.done = true;
plugins: [
function (b) {
var t = b.types;
return {
visitor: {
Program: function(path) {
if (this.done) return;
// if (false) { if (true) 42; } else 23;
var inner = t.ifStatement(t.booleanLiteral(true), t.expressionStatement(t.numericLiteral(42)), null);
var outer = t.ifStatement(t.booleanLiteral(false), inner, t.expressionStatement(t.numericLiteral(23)));
path.replaceWith(t.program([outer]));
this.done = true;
}
}
}
}
}
},
],
});
assert.equal(eval(res.code), 23);