Skip to content

Commit

Permalink
Add "extensions" option
Browse files Browse the repository at this point in the history
- It can be set in programmatic option, babel.config.json, .babelrc.json and in presets
- Files not matching any extension are ignored
- If no filename is given, the file is not ignored
- "*" is supported as a catch-all extension

For backward compatibility reasons, this option defaults to ["*"].
  • Loading branch information
nicolo-ribaudo committed Oct 9, 2020
1 parent eab3c06 commit 987577e
Show file tree
Hide file tree
Showing 10 changed files with 191 additions and 24 deletions.
13 changes: 10 additions & 3 deletions packages/babel-core/src/config/full.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import gensync, { type Handler } from "gensync";
import { forwardAsync } from "../gensync-utils/async";

import { mergeOptions } from "./util";
import * as context from "../index";
import * as babelContext from "../index";
import Plugin from "./plugin";
import { getItemDescriptor } from "./item";
import {
Expand Down Expand Up @@ -70,7 +70,7 @@ export default gensync<[any], ResolvedConfig | null>(function* loadFullConfig(
return null;
}

const optionDefaults = {};
const optionDefaults: ValidatedOptions = {};

const { plugins, presets } = options;

Expand Down Expand Up @@ -161,6 +161,8 @@ export default gensync<[any], ResolvedConfig | null>(function* loadFullConfig(
const opts: Object = optionDefaults;
mergeOptions(opts, options);

if (!isCompilableFile(context.filename, opts.extensions)) return null;

yield* enhanceError(context, function* loadPluginDescriptors() {
pluginDescriptorsByPass[0].unshift(...initialPluginsDescriptors);

Expand Down Expand Up @@ -229,7 +231,7 @@ const loadDescriptor = makeWeakCache(function* (
let item = value;
if (typeof value === "function") {
const api = {
...context,
...babelContext,
...makeAPI(cache),
};
try {
Expand Down Expand Up @@ -395,3 +397,8 @@ function chain(a, b) {
}
};
}

function isCompilableFile(filename, extensions) {
if (filename == null) return true;
return extensions.some(ext => ext === "*" || filename.endsWith(ext));
}
8 changes: 7 additions & 1 deletion packages/babel-core/src/config/partial.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,11 +115,17 @@ export default function* loadPrivatePartialConfig(
const configChain = yield* buildRootChain(args, context);
if (!configChain) return null;

const options = {};
const options: ValidatedOptions = {};
configChain.options.forEach(opts => {
mergeOptions(options, opts);
});

// If the programmatic options or config files don't set the "extensions"
// option, default to ["*"] for backward compatibility reasons.
// TODO(Babel 8): The default should be babel.DEFAULT_EXTENSIONS
// TODO: Use ??= once flow supports it.
options.extensions = options.extensions ?? ["*"];

// Tack the passes onto the object itself so that, if this object is
// passed back to Babel a second time, it will be in the right structure
// to not change behavior.
Expand Down
9 changes: 9 additions & 0 deletions packages/babel-core/src/config/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

import type { ValidatedOptions } from "./validation/options";

function unique<T>(...args: T[]): T[] {
return Array.from(new Set(args));
}

export function mergeOptions(
target: ValidatedOptions,
source: ValidatedOptions,
Expand All @@ -15,6 +19,11 @@ export function mergeOptions(
const generatorOpts = source.generatorOpts;
const targetObj = (target.generatorOpts = target.generatorOpts || {});
mergeDefaultFields(targetObj, generatorOpts);
} else if (k === "extensions" && target.extensions && source.extensions) {
target.extensions = unique(
...(target.extensions ?? []),
...source.extensions,
);
} else {
const val = source[k];
if (val !== undefined) target[k] = (val: any);
Expand Down
46 changes: 34 additions & 12 deletions packages/babel-core/src/config/validation/option-assertions.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import type {
ConfigFileSearch,
BabelrcSearch,
IgnoreList,
FileExtension,
IgnoreItem,
PluginList,
PluginItem,
Expand Down Expand Up @@ -217,17 +217,20 @@ export function assertArray(
return value;
}

export function assertIgnoreList(
loc: OptionPath,
value: mixed,
): IgnoreList | void {
const arr = assertArray(loc, value);
if (arr) {
arr.forEach((item, i) => assertIgnoreItem(access(loc, i), item));
}
return (arr: any);
function createArrayAssertion<T>(
assertValid: (loc: GeneralPath, value: mixed) => T,
): (loc: GeneralPath, value: mixed) => $ReadOnlyArray<T> | void {
return (loc, value) => {
const arr = assertArray(loc, value);
if (arr) arr.forEach((item, i) => assertValid(access(loc, i), item));
return (arr: any);
};
}
function assertIgnoreItem(loc: GeneralPath, value: mixed): IgnoreItem {

export const assertIgnoreList = createArrayAssertion(function assertIgnoreItem(
loc: GeneralPath,
value: mixed,
): IgnoreItem {
if (
typeof value !== "string" &&
typeof value !== "function" &&
Expand All @@ -240,7 +243,26 @@ function assertIgnoreItem(loc: GeneralPath, value: mixed): IgnoreItem {
);
}
return value;
}
});

export const assertExtensionsList = createArrayAssertion(
function assertExtension(
loc: GeneralPath,
value: mixed,
): FileExtension | "*" {
if (value === "*") return value;

if (typeof value !== "string") {
throw new Error(
`${msg(loc)} must be an array of string values, or undefined`,
);
}
if (!value.startsWith(".")) {
throw new Error(`${msg(loc)} must start with a '.' (dot)`);
}
return (value: any);
},
);

export function assertConfigApplicableTest(
loc: OptionPath,
Expand Down
13 changes: 12 additions & 1 deletion packages/babel-core/src/config/validation/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
assertCallerMetadata,
assertInputSourceMap,
assertIgnoreList,
assertExtensionsList,
assertPluginList,
assertConfigApplicableTest,
assertConfigFileSearch,
Expand Down Expand Up @@ -114,6 +115,10 @@ const COMMON_VALIDATORS: ValidatorSet = {
$PropertyType<ValidatedOptions, "exclude">,
>),

extensions: (assertExtensionsList: Validator<
$PropertyType<ValidatedOptions, "extensions">,
>),

retainLines: (assertBoolean: Validator<
$PropertyType<ValidatedOptions, "retainLines">,
>),
Expand Down Expand Up @@ -195,9 +200,12 @@ export type ValidatedOptions = {

extends?: string,
env?: EnvSet<ValidatedOptions>,
overrides?: OverridesList,

// Options to enable/disable processing of some files
ignore?: IgnoreList,
only?: IgnoreList,
overrides?: OverridesList,
extensions?: ExtensionsList,

// Generally verify if a given config object should be applied to the given file.
test?: ConfigApplicableTest,
Expand Down Expand Up @@ -252,6 +260,9 @@ export type EnvSet<T> = {
export type IgnoreItem = string | Function | RegExp;
export type IgnoreList = $ReadOnlyArray<IgnoreItem>;

export opaque type FileExtension = string;
export type ExtensionsList = $ReadOnlyArray<FileExtension | "*">;

export type PluginOptions = {} | void | false;
export type PluginTarget = string | {} | Function;
export type PluginItem =
Expand Down
12 changes: 5 additions & 7 deletions packages/babel-core/src/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// @flow

import type { ExtensionsList } from "./config/validation/options";

export { default as File } from "./transformation/file/file";
export { default as buildExternalHelpers } from "./tools/build-external-helpers";
export { resolvePlugin, resolvePreset } from "./config/files";
Expand Down Expand Up @@ -41,13 +43,9 @@ export { parse, parseSync, parseAsync } from "./parse";
* Recommended set of compilable extensions. Not used in @babel/core directly, but meant as
* as an easy source for tooling making use of @babel/core.
*/
export const DEFAULT_EXTENSIONS = Object.freeze([
".js",
".jsx",
".es6",
".es",
".mjs",
]);
export const DEFAULT_EXTENSIONS: ExtensionsList = Object.freeze(
([".js", ".jsx", ".es6", ".es", ".mjs"]: any),
);

// For easier backward-compatibility, provide an API like the one we exposed in Babel 6.
import { loadOptions } from "./config";
Expand Down
109 changes: 109 additions & 0 deletions packages/babel-core/test/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -688,6 +688,115 @@ describe("api", function () {
]);
});

it("extensions option", function () {
return Promise.all([
transformAsync("", {
filename: "bar.js",
}).then(assertNotIgnored),

transformAsync("", {
filename: "bar.mjs",
}).then(assertNotIgnored),

transformAsync("", {
filename: "bar.jsx",
}).then(assertNotIgnored),

transformAsync("", {
filename: undefined,
}).then(assertNotIgnored),

transformAsync("", {
filename: "bar.ts",
}).then(assertNotIgnored),

transformAsync("", {
extensions: [".js", ".mjs"],
filename: "bar.ts",
}).then(assertIgnored),

transformAsync("", {
extensions: [".ts"],
filename: "bar.ts",
}).then(assertNotIgnored),

transformAsync("", {
extensions: [".ts"],
filename: "bar.js",
}).then(assertIgnored),

transformAsync("", {
extensions: ["*"],
filename: "bar.ts",
}).then(assertNotIgnored),

transformAsync("", {
extensions: ["*"],
filename: "bar.js",
}).then(assertNotIgnored),

transformAsync("", {
extensions: ["*"],
filename: undefined,
}).then(assertNotIgnored),

transformAsync("", {
extensions: ["*"],
filename: "bar",
}).then(assertNotIgnored),

// Presets can add supported extensions but they don't prevent the
// default value of ["*"] from being set.

transformAsync("", {
extensions: [".js"],
filename: "bar.js",
presets: [() => ({ extensions: [".ts"], plugins: [] })],
}).then(assertNotIgnored),

transformAsync("", {
extensions: [".js"],
filename: "bar.ts",
presets: [() => ({ extensions: [".ts"], plugins: [] })],
}).then(assertNotIgnored),

transformAsync("", {
extensions: [".js"],
filename: "bar.tsx",
presets: [() => ({ extensions: [".ts"], plugins: [] })],
}).then(assertIgnored),

transformAsync("", {
filename: "bar.tsx",
presets: [() => ({ extensions: [".ts"], plugins: [] })],
}).then(assertNotIgnored),

// Test with 'extensions' option in config file

transformAsync("", {
configFile: `${__dirname}/fixtures/api/config-with-ts-extension/babel.config.json`,
filename: "foo.ts",
}).then(assertNotIgnored),

transformAsync("", {
configFile: `${__dirname}/fixtures/api/config-with-ts-extension/babel.config.json`,
filename: "foo.tsx",
}).then(assertIgnored),

transformAsync("", {
extensions: [".tsx"],
configFile: `${__dirname}/fixtures/api/config-with-ts-extension/babel.config.json`,
filename: "foo.tsx",
}).then(assertNotIgnored),

transformAsync("", {
extensions: ["*"],
configFile: `${__dirname}/fixtures/api/config-with-ts-extension/babel.config.json`,
filename: "foo.tsx",
}).then(assertNotIgnored),
]);
});

describe("env option", function () {
const oldBabelEnv = process.env.BABEL_ENV;
const oldNodeEnv = process.env.NODE_ENV;
Expand Down
1 change: 1 addition & 0 deletions packages/babel-core/test/config-chain.js
Original file line number Diff line number Diff line change
Expand Up @@ -1011,6 +1011,7 @@ describe("buildConfigChain", function () {
plugins: [],
presets: [],
cloneInputAst: true,
extensions: ["*"],
});
const realEnv = process.env.NODE_ENV;
const realBabelEnv = process.env.BABEL_ENV;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extensions": [".ts"]
}
1 change: 1 addition & 0 deletions packages/babel-node/src/_babel-node.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ const _eval = function (code, filename) {
if (!code) return undefined;

code = babel.transform(code, {
extensions: ["*"],
filename: filename,
presets: program.presets,
plugins: (program.plugins || []).concat([replPlugin]),
Expand Down

0 comments on commit 987577e

Please sign in to comment.