Skip to content

Commit

Permalink
Hide internal @babel/core functions in config errors
Browse files Browse the repository at this point in the history
  • Loading branch information
nicolo-ribaudo committed Aug 21, 2022
1 parent a7c0212 commit b61826d
Show file tree
Hide file tree
Showing 21 changed files with 523 additions and 57 deletions.
52 changes: 36 additions & 16 deletions packages/babel-core/src/config/config-chain.ts
Expand Up @@ -14,6 +14,9 @@ import pathPatternToRegex from "./pattern-to-regex";
import { ConfigPrinter, ChainFormatter } from "./printer";
import type { ReadonlyDeepArray } from "./helpers/deep-array";

import { endHiddenCallStack } from "../errors/rewrite-stack-trace";
import ConfigError from "../errors/config-error";

const debug = buildDebug("babel:config:config-chain");

import {
Expand Down Expand Up @@ -329,23 +332,23 @@ const validateConfigFile = makeWeakCacheSync(
(file: ConfigFile): ValidatedFile => ({
filepath: file.filepath,
dirname: file.dirname,
options: validate("configfile", file.options),
options: validate("configfile", file.options, file.filepath),
}),
);

const validateBabelrcFile = makeWeakCacheSync(
(file: ConfigFile): ValidatedFile => ({
filepath: file.filepath,
dirname: file.dirname,
options: validate("babelrcfile", file.options),
options: validate("babelrcfile", file.options, file.filepath),
}),
);

const validateExtendFile = makeWeakCacheSync(
(file: ConfigFile): ValidatedFile => ({
filepath: file.filepath,
dirname: file.dirname,
options: validate("extendsfile", file.options),
options: validate("extendsfile", file.options, file.filepath),
}),
);

Expand Down Expand Up @@ -528,7 +531,11 @@ function buildOverrideEnvDescriptors(
}

function makeChainWalker<
ArgT extends { options: ValidatedOptions; dirname: string },
ArgT extends {
options: ValidatedOptions;
dirname: string;
filepath?: string;
},
>({
root,
env,
Expand Down Expand Up @@ -559,7 +566,7 @@ function makeChainWalker<
files?: Set<ConfigFile>,
baseLogger?: ConfigPrinter,
) => Handler<ConfigChain | null> {
return function* (input, context, files = new Set(), baseLogger) {
return function* chainWalker(input, context, files = new Set(), baseLogger) {
const { dirname } = input;

const flattenedConfigs: Array<{
Expand All @@ -569,15 +576,18 @@ function makeChainWalker<
}> = [];

const rootOpts = root(input);
if (configIsApplicable(rootOpts, dirname, context)) {
if (configIsApplicable(rootOpts, dirname, context, input.filepath)) {
flattenedConfigs.push({
config: rootOpts,
envName: undefined,
index: undefined,
});

const envOpts = env(input, context.envName);
if (envOpts && configIsApplicable(envOpts, dirname, context)) {
if (
envOpts &&
configIsApplicable(envOpts, dirname, context, input.filepath)
) {
flattenedConfigs.push({
config: envOpts,
envName: context.envName,
Expand All @@ -587,7 +597,7 @@ function makeChainWalker<

(rootOpts.options.overrides || []).forEach((_, index) => {
const overrideOps = overrides(input, index);
if (configIsApplicable(overrideOps, dirname, context)) {
if (configIsApplicable(overrideOps, dirname, context, input.filepath)) {
flattenedConfigs.push({
config: overrideOps,
index,
Expand All @@ -597,7 +607,12 @@ function makeChainWalker<
const overrideEnvOpts = overridesEnv(input, index, context.envName);
if (
overrideEnvOpts &&
configIsApplicable(overrideEnvOpts, dirname, context)
configIsApplicable(
overrideEnvOpts,
dirname,
context,
input.filepath,
)
) {
flattenedConfigs.push({
config: overrideEnvOpts,
Expand Down Expand Up @@ -789,25 +804,27 @@ function configIsApplicable(
{ options }: OptionsAndDescriptors,
dirname: string,
context: ConfigContext,
configName: string,
): boolean {
return (
(options.test === undefined ||
configFieldIsApplicable(context, options.test, dirname)) &&
configFieldIsApplicable(context, options.test, dirname, configName)) &&
(options.include === undefined ||
configFieldIsApplicable(context, options.include, dirname)) &&
configFieldIsApplicable(context, options.include, dirname, configName)) &&
(options.exclude === undefined ||
!configFieldIsApplicable(context, options.exclude, dirname))
!configFieldIsApplicable(context, options.exclude, dirname, configName))
);
}

function configFieldIsApplicable(
context: ConfigContext,
test: ConfigApplicableTest,
dirname: string,
configName: string,
): boolean {
const patterns = Array.isArray(test) ? test : [test];

return matchesPatterns(context, patterns, dirname);
return matchesPatterns(context, patterns, dirname, configName);
}

/**
Expand Down Expand Up @@ -872,9 +889,10 @@ function matchesPatterns(
context: ConfigContext,
patterns: IgnoreList,
dirname: string,
configName?: string,
): boolean {
return patterns.some(pattern =>
matchPattern(pattern, dirname, context.filename, context),
matchPattern(pattern, dirname, context.filename, context, configName),
);
}

Expand All @@ -883,18 +901,20 @@ function matchPattern(
dirname: string,
pathToTest: unknown,
context: ConfigContext,
configName?: string,
): boolean {
if (typeof pattern === "function") {
return !!pattern(pathToTest, {
return !!endHiddenCallStack(pattern)(pathToTest, {
dirname,
envName: context.envName,
caller: context.caller,
});
}

if (typeof pathToTest !== "string") {
throw new Error(
throw new ConfigError(
`Configuration contains string/RegExp pattern, but no filename was passed to Babel`,
configName,
);
}

Expand Down
54 changes: 34 additions & 20 deletions packages/babel-core/src/config/files/configuration.ts
Expand Up @@ -13,10 +13,12 @@ import loadCjsOrMjsDefault from "./module-types";
import pathPatternToRegex from "../pattern-to-regex";
import type { FilePackageData, RelativeConfig, ConfigFile } from "./types";
import type { CallerMetadata } from "../validation/options";
import ConfigError from "../../errors/config-error";

import * as fs from "../../gensync-utils/fs";

import { createRequire } from "module";
import { endHiddenCallStack } from "../../errors/rewrite-stack-trace";
const require = createRequire(import.meta.url);

const debug = buildDebug("babel:config:loading:files:configuration");
Expand Down Expand Up @@ -112,7 +114,7 @@ function* loadOneConfig(
);
const config = configs.reduce((previousConfig: ConfigFile | null, config) => {
if (config && previousConfig) {
throw new Error(
throw new ConfigError(
`Multiple configuration files found. Please remove one:\n` +
` - ${path.basename(previousConfig.filepath)}\n` +
` - ${config.filepath}\n` +
Expand All @@ -139,7 +141,10 @@ export function* loadConfig(

const conf = yield* readConfig(filepath, envName, caller);
if (!conf) {
throw new Error(`Config file ${filepath} contains no configuration data`);
throw new ConfigError(
`Config file contains no configuration data`,
filepath,
);
}

debug("Loaded config %o from %o.", name, dirname);
Expand Down Expand Up @@ -197,9 +202,6 @@ const readConfigJS = makeStrongCache(function* readConfigJS(
"You appear to be using a native ECMAScript module configuration " +
"file, which is only supported when running Babel asynchronously.",
);
} catch (err) {
err.message = `${filepath}: Error while loading config - ${err.message}`;
throw err;
} finally {
LOADING_CONFIGS.delete(filepath);
}
Expand All @@ -209,29 +211,33 @@ const readConfigJS = makeStrongCache(function* readConfigJS(
// @ts-expect-error - if we want to make it possible to use async configs
yield* [];

options = (options as any as (api: ConfigAPI) => {})(makeConfigAPI(cache));
options = endHiddenCallStack(options as any as (api: ConfigAPI) => {})(
makeConfigAPI(cache),
);

assertCache = true;
}

if (!options || typeof options !== "object" || Array.isArray(options)) {
throw new Error(
`${filepath}: Configuration should be an exported JavaScript object.`,
throw new ConfigError(
`Configuration should be an exported JavaScript object.`,
filepath,
);
}

// @ts-expect-error todo(flow->ts)
if (typeof options.then === "function") {
throw new Error(
throw new ConfigError(
`You appear to be using an async configuration, ` +
`which your current version of Babel does not support. ` +
`We may add support for this in the future, ` +
`but if you're on the most recent version of @babel/core and still ` +
`seeing this error, then you'll need to synchronously return your config.`,
filepath,
);
}

if (assertCache && !cache.configured()) throwConfigError();
if (assertCache && !cache.configured()) throwConfigError(filepath);

return {
filepath,
Expand All @@ -247,7 +253,7 @@ const packageToBabelConfig = makeWeakCacheSync(
if (typeof babel === "undefined") return null;

if (typeof babel !== "object" || Array.isArray(babel) || babel === null) {
throw new Error(`${file.filepath}: .babel property must be an object`);
throw new ConfigError(`.babel property must be an object`, file.filepath);
}

return {
Expand All @@ -263,17 +269,19 @@ const readConfigJSON5 = makeStaticFileCache((filepath, content): ConfigFile => {
try {
options = json5.parse(content);
} catch (err) {
err.message = `${filepath}: Error while parsing config - ${err.message}`;
throw err;
throw new ConfigError(
`Error while parsing config - ${err.message}`,
filepath,
);
}

if (!options) throw new Error(`${filepath}: No config detected`);
if (!options) throw new ConfigError(`No config detected`, filepath);

if (typeof options !== "object") {
throw new Error(`${filepath}: Config returned typeof ${typeof options}`);
throw new ConfigError(`Config returned typeof ${typeof options}`, filepath);
}
if (Array.isArray(options)) {
throw new Error(`${filepath}: Expected config object but found array`);
throw new ConfigError(`Expected config object but found array`, filepath);
}

delete options["$schema"];
Expand All @@ -294,7 +302,10 @@ const readIgnoreConfig = makeStaticFileCache((filepath, content) => {

for (const pattern of ignorePatterns) {
if (pattern[0] === "!") {
throw new Error(`Negation of file paths is not supported.`);
throw new ConfigError(
`Negation of file paths is not supported.`,
filepath,
);
}
}

Expand Down Expand Up @@ -324,8 +335,9 @@ export function* resolveShowConfigPath(
return null;
}

function throwConfigError(): never {
throw new Error(`\
function throwConfigError(filepath: string): never {
throw new ConfigError(
`\
Caching was left unconfigured. Babel's plugins, presets, and .babelrc.js files can be configured
for various types of caching, using the first param of their handler functions:
Expand Down Expand Up @@ -358,5 +370,7 @@ module.exports = function(api) {
// Return the value that will be cached.
return { };
};`);
};`,
filepath,
);
}
12 changes: 8 additions & 4 deletions packages/babel-core/src/config/files/module-types.ts
Expand Up @@ -5,6 +5,9 @@ import { pathToFileURL } from "url";
import { createRequire } from "module";
import semver from "semver";

import { endHiddenCallStack } from "../../errors/rewrite-stack-trace";
import ConfigError from "../../errors/config-error";

const require = createRequire(import.meta.url);

let import_: ((specifier: string | URL) => any) | undefined;
Expand Down Expand Up @@ -40,7 +43,7 @@ export default function* loadCjsOrMjsDefault(
if (yield* isAsync()) {
return yield* waitFor(loadMjsDefault(filepath));
}
throw new Error(asyncError);
throw new ConfigError(asyncError, filepath);
}
}

Expand All @@ -56,7 +59,7 @@ function guessJSModuleType(filename: string): "cjs" | "mjs" | "unknown" {
}

function loadCjsDefault(filepath: string, fallbackToTranspiledModule: boolean) {
const module = require(filepath) as any;
const module = endHiddenCallStack(require)(filepath) as any;
return module?.__esModule
? // TODO (Babel 8): Remove "module" and "undefined" fallback
module.default || (fallbackToTranspiledModule ? module : undefined)
Expand All @@ -65,14 +68,15 @@ function loadCjsDefault(filepath: string, fallbackToTranspiledModule: boolean) {

async function loadMjsDefault(filepath: string) {
if (!import_) {
throw new Error(
throw new ConfigError(
"Internal error: Native ECMAScript modules aren't supported" +
" by this platform.\n",
filepath,
);
}

// import() expects URLs, not file paths.
// https://github.com/nodejs/node/issues/31710
const module = await import_(pathToFileURL(filepath));
const module = await endHiddenCallStack(import_)(pathToFileURL(filepath));
return module.default;
}

0 comments on commit b61826d

Please sign in to comment.