Merge pull request #4892 from kaicataldo/babelrcjs

Add support for .babelrc.js files
This commit is contained in:
Henry Zhu 2017-03-07 16:58:00 -05:00 committed by GitHub
commit 02473a72c1
32 changed files with 466 additions and 153 deletions

View File

@ -1,4 +1,3 @@
import type Logger from "../logger";
import resolve from "../../../helpers/resolve";
import json5 from "json5";
@ -8,9 +7,10 @@ import fs from "fs";
const existsCache = {};
const jsonCache = {};
const BABELIGNORE_FILENAME = ".babelignore";
const BABELRC_FILENAME = ".babelrc";
const BABELRC_JS_FILENAME = ".babelrc.js";
const PACKAGE_FILENAME = "package.json";
const BABELIGNORE_FILENAME = ".babelignore";
function exists(filename) {
const cached = existsCache[filename];
@ -46,7 +46,12 @@ class ConfigChainBuilder {
this.log = log;
}
findConfigs(loc) {
errorMultipleConfigs(loc1: string, loc2: string) {
throw new Error(`Multiple configuration files found. Please remove one:\n- ${
loc1}\n- ${loc2}`);
}
findConfigs(loc: string) {
if (!loc) return;
if (!path.isAbsolute(loc)) {
@ -59,15 +64,26 @@ class ConfigChainBuilder {
while (loc !== (loc = path.dirname(loc))) {
if (!foundConfig) {
const configLoc = path.join(loc, BABELRC_FILENAME);
if (exists(configLoc)) {
this.addConfig(configLoc);
foundConfig = true;
}
const configJSLoc = path.join(loc, BABELRC_JS_FILENAME);
const pkgLoc = path.join(loc, PACKAGE_FILENAME);
if (!foundConfig && exists(pkgLoc)) {
foundConfig = this.addConfig(pkgLoc, "babel", JSON);
}
const configLocs = [configLoc, configJSLoc, pkgLoc];
const foundConfigs = configLocs.reduce((arr, config) => {
if (exists(config)) {
const configAdded = config === pkgLoc
? this.addConfig(config, "babel", JSON)
: this.addConfig(config);
if (configAdded && arr.length) {
this.errorMultipleConfigs(arr.pop(), config);
}
arr.push(config);
}
return arr;
}, []);
foundConfig = !!foundConfigs.length;
}
if (!foundIgnore) {
@ -82,7 +98,7 @@ class ConfigChainBuilder {
}
}
addIgnoreConfig(loc) {
addIgnoreConfig(loc: string) {
const file = fs.readFileSync(loc, "utf8");
let lines = file.split("\n");
@ -106,15 +122,35 @@ class ConfigChainBuilder {
this.resolvedConfigs.push(loc);
const content = fs.readFileSync(loc, "utf8");
let options;
if (path.extname(loc) === ".js") {
try {
const configModule = require(loc);
options = configModule && configModule.__esModule ? configModule.default : configModule;
} catch (err) {
err.message = `${loc}: Error while loading config - ${err.message}`;
throw err;
}
try {
options = jsonCache[content] = jsonCache[content] || json.parse(content);
if (key) options = options[key];
} catch (err) {
err.message = `${loc}: Error while parsing JSON - ${err.message}`;
throw err;
if (!options || typeof options !== "object") {
throw new Error("Configuration should be an exported JavaScript object.");
}
} else {
const content = fs.readFileSync(loc, "utf8");
try {
options = jsonCache[content] = jsonCache[content] || json.parse(content);
} catch (err) {
err.message = `${loc}: Error while parsing JSON - ${err.message}`;
throw err;
}
}
if (key) {
if (!options[key]) {
return false;
}
options = options[key];
}
this.mergeConfig({

View File

@ -294,4 +294,253 @@ describe("buildConfigChain", function () {
assert.deepEqual(chain, expected);
});
it("js-config", function () {
const chain = buildConfigChain({
filename: fixture("js-config", "src.js"),
});
const expected = [
{
options: {
plugins: [
"foo",
"bar",
],
},
alias: fixture("js-config", ".babelrc.js"),
loc: fixture("js-config", ".babelrc.js"),
dirname: fixture("js-config"),
},
{
options: {
ignore: [
"root-ignore",
],
},
alias: fixture(".babelignore"),
loc: fixture(".babelignore"),
dirname: fixture(),
},
{
options: {
filename: fixture("js-config", "src.js"),
},
alias: "base",
loc: "base",
dirname: fixture("js-config"),
},
];
assert.deepEqual(chain, expected);
});
it("js-config-default - should read transpiled export default", function () {
const chain = buildConfigChain({
filename: fixture("js-config-default", "src.js"),
});
const expected = [
{
options: {
plugins: [
"foo",
"bar",
],
},
alias: fixture("js-config-default", ".babelrc.js"),
loc: fixture("js-config-default", ".babelrc.js"),
dirname: fixture("js-config-default"),
},
{
options: {
ignore: [
"root-ignore",
],
},
alias: fixture(".babelignore"),
loc: fixture(".babelignore"),
dirname: fixture(),
},
{
options: {
filename: fixture("js-config-default", "src.js"),
},
alias: "base",
loc: "base",
dirname: fixture("js-config-default"),
},
];
assert.deepEqual(chain, expected);
});
it("js-config-extended", function () {
const chain = buildConfigChain({
filename: fixture("js-config-extended", "src.js"),
});
const expected = [
{
options: {
plugins: [
"extended",
],
},
alias: fixture("extended.babelrc.json"),
loc: fixture("extended.babelrc.json"),
dirname: fixture(),
},
{
options: {
plugins: [
"foo",
"bar",
],
},
alias: fixture("js-config-extended", ".babelrc.js"),
loc: fixture("js-config-extended", ".babelrc.js"),
dirname: fixture("js-config-extended"),
},
{
options: {
ignore: [
"root-ignore",
],
},
alias: fixture(".babelignore"),
loc: fixture(".babelignore"),
dirname: fixture(),
},
{
options: {
filename: fixture("js-config-extended", "src.js"),
},
alias: "base",
loc: "base",
dirname: fixture("js-config-extended"),
},
];
assert.deepEqual(chain, expected);
});
it("json-pkg-config-no-babel - should not throw if" +
" package.json doesn't contain a `babel` field", function () {
const chain = buildConfigChain({
filename: fixture("json-pkg-config-no-babel", "src.js"),
});
const expected = [
{
options: {
plugins: [
"json",
],
},
alias: fixture("json-pkg-config-no-babel", ".babelrc"),
loc: fixture("json-pkg-config-no-babel", ".babelrc"),
dirname: fixture("json-pkg-config-no-babel"),
},
{
options: {
ignore: [
"root-ignore",
],
},
alias: fixture(".babelignore"),
loc: fixture(".babelignore"),
dirname: fixture(),
},
{
options: {
filename: fixture("json-pkg-config-no-babel", "src.js"),
},
alias: "base",
loc: "base",
dirname: fixture("json-pkg-config-no-babel"),
},
];
assert.deepEqual(chain, expected);
});
it("js-json-config - should throw an error if both a .babelrc" +
" and a .babelrc.js are present", function () {
assert.throws(
function () {
buildConfigChain({
filename: fixture("js-json-config", "src.js"),
});
},
/Multiple configuration files found\.(.|\n)*\.babelrc(.|\n)*\.babelrc\.js/
);
});
it("js-pkg-config - should throw an error if both a .babelrc.js" +
" and a package.json with a babel field are present", function () {
assert.throws(
function () {
buildConfigChain({
filename: fixture("js-pkg-config", "src.js"),
});
},
/Multiple configuration files found\.(.|\n)*\.babelrc\.js(.|\n)*package\.json/
);
});
it("json-pkg-config - should throw an error if both a .babelrc" +
" and a package.json with a babel field are present", function () {
assert.throws(
function () {
buildConfigChain({
filename: fixture("json-pkg-config", "src.js"),
});
},
/Multiple configuration files found\.(.|\n)*\.babelrc(.|\n)*package\.json/
);
});
it("js-config-error", function () {
assert.throws(
function () {
buildConfigChain({
filename: fixture("js-config-error", "src.js"),
});
},
/Error while loading config/
);
});
it("js-config-error2", function () {
assert.throws(
function () {
buildConfigChain({
filename: fixture("js-config-error2", "src.js"),
});
},
/Configuration should be an exported JavaScript object/
);
});
it("js-config-error3", function () {
assert.throws(
function () {
buildConfigChain({
filename: fixture("js-config-error3", "src.js"),
});
},
/Configuration should be an exported JavaScript object/
);
});
it("json-config-error", function () {
assert.throws(
function () {
buildConfigChain({
filename: fixture("json-config-error", "src.js"),
});
},
/Error while parsing JSON/
);
});
});

View File

@ -0,0 +1,10 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
var plugins = ["foo", "bar"];
exports.default = {
plugins: plugins
};

View File

@ -0,0 +1 @@
// empty

View File

@ -0,0 +1,2 @@
throw new Error("Something bad happened!");
module.exports = {}

View File

@ -0,0 +1 @@
// empty

View File

@ -0,0 +1 @@
module.exports = '';

View File

@ -0,0 +1 @@
// empty

View File

@ -0,0 +1 @@
module.exports = null;

View File

@ -0,0 +1 @@
// empty

View File

@ -0,0 +1,6 @@
var plugins = ["foo", "bar"];
module.exports = {
extends: "../extended.babelrc.json",
plugins: plugins
}

View File

@ -0,0 +1 @@
// empty

View File

@ -0,0 +1,5 @@
var plugins = ["foo", "bar"];
module.exports = {
plugins: plugins
}

View File

@ -0,0 +1 @@
// empty

View File

@ -0,0 +1,5 @@
{
"plugins": [
"json"
]
}

View File

@ -0,0 +1,5 @@
module.exports = {
plugins: [
"js"
]
}

View File

@ -0,0 +1 @@
// empty

View File

@ -0,0 +1,5 @@
module.exports = {
plugins: [
"js"
]
}

View File

@ -0,0 +1,3 @@
{
"babel": {}
}

View File

@ -0,0 +1 @@
// empty

View File

@ -0,0 +1,3 @@
{
"bad: "json"
}

View File

@ -0,0 +1 @@
// empty

View File

@ -0,0 +1,5 @@
{
"plugins": [
"json"
]
}

View File

@ -0,0 +1 @@
{}

View File

@ -0,0 +1 @@
// empty

View File

@ -0,0 +1,5 @@
{
"plugins": [
"json"
]
}

View File

@ -0,0 +1,3 @@
{
"babel": {}
}

View File

@ -0,0 +1 @@
// empty

View File

@ -10,9 +10,7 @@ import type { Format } from "./printer";
*/
class Generator extends Printer {
constructor(ast, opts, code) {
opts = opts || {};
constructor(ast, opts = {}, code) {
const tokens = ast.tokens || [];
const format = normalizeOptions(code, opts, tokens);
const map = opts.sourceMaps ? new SourceMap(opts, code) : null;

View File

@ -34,12 +34,8 @@ export function NullableTypeAnnotation(node: Object, parent: Object): boolean {
export { NullableTypeAnnotation as FunctionTypeAnnotation };
export function UpdateExpression(node: Object, parent: Object): boolean {
if (t.isMemberExpression(parent) && parent.object === node) {
// (foo++).test()
return true;
}
return false;
// (foo++).test()
return t.isMemberExpression(parent) && parent.object === node;
}
export function ObjectExpression(node: Object, parent: Object, printStack: Array<Object>): boolean {
@ -51,15 +47,11 @@ export function DoExpression(node: Object, parent: Object, printStack: Array<Obj
}
export function Binary(node: Object, parent: Object): boolean {
if ((t.isCallExpression(parent) || t.isNewExpression(parent)) && parent.callee === node) {
return true;
}
if (t.isUnaryLike(parent)) {
return true;
}
if (t.isMemberExpression(parent) && parent.object === node) {
if (
((t.isCallExpression(parent) || t.isNewExpression(parent)) && parent.callee === node) ||
t.isUnaryLike(parent) ||
(t.isMemberExpression(parent) && parent.object === node)
) {
return true;
}
@ -70,12 +62,11 @@ export function Binary(node: Object, parent: Object): boolean {
const nodeOp = node.operator;
const nodePos = PRECEDENCE[nodeOp];
if (parentPos > nodePos) {
return true;
}
// Logical expressions with the same precedence don't need parens.
if (parentPos === nodePos && parent.right === node && !t.isLogicalExpression(parent)) {
if (
// Logical expressions with the same precedence don't need parens.
(parentPos === nodePos && parent.right === node && !t.isLogicalExpression(parent)) ||
parentPos > nodePos
) {
return true;
}
}
@ -84,55 +75,27 @@ export function Binary(node: Object, parent: Object): boolean {
}
export function BinaryExpression(node: Object, parent: Object): boolean {
if (node.operator === "in") {
// let i = (1 in []);
if (t.isVariableDeclarator(parent)) {
return true;
}
// for ((1 in []);;);
if (t.isFor(parent)) {
return true;
}
}
return false;
// let i = (1 in []);
// for ((1 in []);;);
return node.operator === "in" && (t.isVariableDeclarator(parent) || t.isFor(parent));
}
export function SequenceExpression(node: Object, parent: Object): boolean {
if (t.isForStatement(parent)) {
if (
// Although parentheses wouldn"t hurt around sequence
// expressions in the head of for loops, traditional style
// dictates that e.g. i++, j++ should not be wrapped with
// parentheses.
return false;
}
if (t.isExpressionStatement(parent) && parent.expression === node) {
return false;
}
if (t.isReturnStatement(parent)) {
return false;
}
if (t.isThrowStatement(parent)) {
return false;
}
if (t.isSwitchStatement(parent) && parent.discriminant === node) {
return false;
}
if (t.isWhileStatement(parent) && parent.test === node) {
return false;
}
if (t.isIfStatement(parent) && parent.test === node) {
return false;
}
if (t.isForInStatement(parent) && parent.right === node) {
t.isForStatement(parent) ||
t.isThrowStatement(parent) ||
t.isReturnStatement(parent) ||
(t.isIfStatement(parent) && parent.test === node) ||
(t.isWhileStatement(parent) && parent.test === node) ||
(t.isForInStatement(parent) && parent.right === node) ||
(t.isSwitchStatement(parent) && parent.discriminant === node) ||
(t.isExpressionStatement(parent) && parent.expression === node)
) {
return false;
}
@ -158,15 +121,9 @@ export function ClassExpression(node: Object, parent: Object, printStack: Array<
}
export function UnaryLike(node: Object, parent: Object): boolean {
if (t.isMemberExpression(parent, { object: node })) {
return true;
}
if (t.isCallExpression(parent, { callee: node }) || t.isNewExpression(parent, { callee: node })) {
return true;
}
return false;
return t.isMemberExpression(parent, { object: node }) ||
t.isCallExpression(parent, { callee: node }) ||
t.isNewExpression(parent, { callee: node });
}
export function FunctionExpression(node: Object, parent: Object, printStack: Array<Object>): boolean {
@ -189,19 +146,12 @@ export function ArrowFunctionExpression(node: Object, parent: Object): boolean {
}
export function ConditionalExpression(node: Object, parent: Object): boolean {
if (t.isUnaryLike(parent)) {
return true;
}
if (t.isBinary(parent)) {
return true;
}
if (t.isConditionalExpression(parent, { test: node })) {
return true;
}
if (t.isAwaitExpression(parent)) {
if (
t.isUnaryLike(parent) ||
t.isBinary(parent) ||
t.isConditionalExpression(parent, { test: node }) ||
t.isAwaitExpression(parent)
) {
return true;
}
@ -227,28 +177,23 @@ function isFirstInStatement(printStack: Array<Object>, {
i--;
let parent = printStack[i];
while (i > 0) {
if (t.isExpressionStatement(parent, { expression: node })) {
if (
t.isExpressionStatement(parent, { expression: node }) ||
t.isTaggedTemplateExpression(parent) ||
considerDefaultExports && t.isExportDefaultDeclaration(parent, { declaration: node }) ||
considerArrow && t.isArrowFunctionExpression(parent, { body: node })
) {
return true;
}
if (t.isTaggedTemplateExpression(parent)) {
return true;
}
if (considerDefaultExports && t.isExportDefaultDeclaration(parent, { declaration: node })) {
return true;
}
if (considerArrow && t.isArrowFunctionExpression(parent, { body: node })) {
return true;
}
if ((t.isCallExpression(parent, { callee: node })) ||
(t.isSequenceExpression(parent) && parent.expressions[0] === node) ||
(t.isMemberExpression(parent, { object: node })) ||
(t.isConditional(parent, { test: node })) ||
(t.isBinary(parent, { left: node })) ||
(t.isAssignmentExpression(parent, { left: node }))) {
if (
t.isCallExpression(parent, { callee: node }) ||
(t.isSequenceExpression(parent) && parent.expressions[0] === node) ||
t.isMemberExpression(parent, { object: node }) ||
t.isConditional(parent, { test: node }) ||
t.isBinary(parent, { left: node }) ||
t.isAssignmentExpression(parent, { left: node })
) {
node = parent;
i--;
parent = printStack[i];

View File

@ -332,9 +332,11 @@ export default class Printer {
this._maybeAddAuxComment(this._insideAux && !oldInAux);
let needsParens = n.needsParens(node, parent, this._printStack);
if (this.format.retainFunctionParens &&
node.type === "FunctionExpression" &&
node.extra && node.extra.parenthesized) {
if (
this.format.retainFunctionParens &&
node.type === "FunctionExpression" &&
node.extra && node.extra.parenthesized
) {
needsParens = true;
}
if (needsParens) this.token("(");

View File

@ -4,19 +4,22 @@
## Example
### Rest Properties
```js
// Rest properties
let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
console.log(x); // 1
console.log(y); // 2
console.log(z); // { a: 3, b: 4 }
```
// Spread properties
### Spread Properties
```js
let n = { x, y, ...z };
console.log(n); // { x: 1, y: 2, a: 3, b: 4 }
```
## Installation
```sh
@ -35,27 +38,6 @@ npm install --save-dev babel-plugin-transform-object-rest-spread
}
```
## Options
This plugin will use babel's `extends` helper, which will polyfill `Object.assign` by default.
* `useBuiltIns` - Do not use Babel's helper's and just transform to use the built-in method (Disabled by default).
```json
{
"plugins": [
["transform-object-rest-spread", { "useBuiltIns": true }]
]
}
```
```js
// source
z = { x, ...y };
// compiled
z = Object.assign({ x }, y);
```
### Via CLI
```sh
@ -70,6 +52,36 @@ require("babel-core").transform("code", {
});
```
## Options
### `useBuiltIns`
`boolean`, defaults to `false`.
By default, this plugin uses Babel's `extends` helper which polyfills `Object.assign`. Enabling this option will use `Object.assign` directly.
**.babelrc**
```json
{
"plugins": [
["transform-object-rest-spread", { "useBuiltIns": true }]
]
}
```
**In**
```js
z = { x, ...y };
```
**Out**
```js
z = Object.assign({ x }, y);
```
## References
* [Proposal: Object Rest/Spread Properties for ECMAScript](https://github.com/sebmarkbage/ecmascript-rest-spread)