From 686dfa87a9349b53d153540624eb03bc5a0f250d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Ribaudo?= Date: Wed, 13 May 2020 00:21:57 +0200 Subject: [PATCH] Hide internal `@babel/core` functions in config errors --- .../babel-core/src/config/config-chain.js | 46 +++-- .../src/config/files/configuration.js | 57 +++--- .../src/config/files/module-types.js | 12 +- .../babel-core/src/config/files/package.js | 15 +- .../babel-core/src/config/files/plugins.js | 4 +- .../src/config/validation/options.js | 26 ++- .../babel-core/src/errors/config-error.js | 9 + .../src/errors/rewrite-stack-trace.js | 151 ++++++++++++++++ packages/babel-core/src/parse.js | 15 +- packages/babel-core/test/errors-stacks.js | 165 ++++++++++++++++++ .../babel.config.js | 12 ++ .../errors/error-config-file/babel.config.js | 4 + .../babel.config.js | 11 ++ .../error-config-function/babel.config.js | 3 + .../errors/invalid-json/babel.config.json | 3 + .../errors/invalid-option/babel.config.json | 3 + .../errors/invalid-pkg-json/package.json | 3 + .../errors/use-exclude/babel.config.js | 4 + .../fixtures/errors/valid/babel.config.json | 1 + 19 files changed, 487 insertions(+), 57 deletions(-) create mode 100644 packages/babel-core/src/errors/config-error.js create mode 100644 packages/babel-core/src/errors/rewrite-stack-trace.js create mode 100644 packages/babel-core/test/errors-stacks.js create mode 100644 packages/babel-core/test/fixtures/errors/error-config-file-more-frames/babel.config.js create mode 100644 packages/babel-core/test/fixtures/errors/error-config-file/babel.config.js create mode 100644 packages/babel-core/test/fixtures/errors/error-config-function-more-frames/babel.config.js create mode 100644 packages/babel-core/test/fixtures/errors/error-config-function/babel.config.js create mode 100644 packages/babel-core/test/fixtures/errors/invalid-json/babel.config.json create mode 100644 packages/babel-core/test/fixtures/errors/invalid-option/babel.config.json create mode 100644 packages/babel-core/test/fixtures/errors/invalid-pkg-json/package.json create mode 100644 packages/babel-core/test/fixtures/errors/use-exclude/babel.config.js create mode 100644 packages/babel-core/test/fixtures/errors/valid/babel.config.json diff --git a/packages/babel-core/src/config/config-chain.js b/packages/babel-core/src/config/config-chain.js index 1af3f35d2371..e0279c68cf80 100644 --- a/packages/babel-core/src/config/config-chain.js +++ b/packages/babel-core/src/config/config-chain.js @@ -13,6 +13,9 @@ import { } from "./validation/options"; import pathPatternToRegex from "./pattern-to-regex"; +import { endHiddenCallStack } from "../errors/rewrite-stack-trace"; +import ConfigError from "../errors/config-error"; + const debug = buildDebug("babel:config:config-chain"); import { @@ -281,7 +284,7 @@ const validateConfigFile = makeWeakCacheSync( (file: ConfigFile): ValidatedFile => ({ filepath: file.filepath, dirname: file.dirname, - options: validate("configfile", file.options), + options: validate("configfile", file.options, file.filepath), }), ); @@ -289,7 +292,7 @@ const validateBabelrcFile = makeWeakCacheSync( (file: ConfigFile): ValidatedFile => ({ filepath: file.filepath, dirname: file.dirname, - options: validate("babelrcfile", file.options), + options: validate("babelrcfile", file.options, file.filepath), }), ); @@ -297,7 +300,7 @@ const validateExtendFile = makeWeakCacheSync( (file: ConfigFile): ValidatedFile => ({ filepath: file.filepath, dirname: file.dirname, - options: validate("extendsfile", file.options), + options: validate("extendsfile", file.options, file.filepath), }), ); @@ -429,29 +432,37 @@ function makeChainWalker({ ConfigContext, Set | void, ) => Handler { - return function*(input, context, files = new Set()) { + return function* chainWalker(input, context, files = new Set()) { const { dirname } = input; const flattenedConfigs = []; const rootOpts = root(input); - if (configIsApplicable(rootOpts, dirname, context)) { + if (configIsApplicable(rootOpts, dirname, context, input.filepath)) { flattenedConfigs.push(rootOpts); const envOpts = env(input, context.envName); - if (envOpts && configIsApplicable(envOpts, dirname, context)) { + if ( + envOpts && + configIsApplicable(envOpts, dirname, context, input.filepath) + ) { flattenedConfigs.push(envOpts); } (rootOpts.options.overrides || []).forEach((_, index) => { const overrideOps = overrides(input, index); - if (configIsApplicable(overrideOps, dirname, context)) { + if (configIsApplicable(overrideOps, dirname, context, input.filepath)) { flattenedConfigs.push(overrideOps); const overrideEnvOpts = overridesEnv(input, index, context.envName); if ( overrideEnvOpts && - configIsApplicable(overrideEnvOpts, dirname, context) + configIsApplicable( + overrideEnvOpts, + dirname, + context, + input.filepath, + ) ) { flattenedConfigs.push(overrideEnvOpts); } @@ -620,14 +631,15 @@ 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)) ); } @@ -635,10 +647,11 @@ 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); } /** @@ -681,9 +694,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), ); } @@ -692,9 +706,10 @@ function matchPattern( dirname, pathToTest, context: ConfigContext, + configName: string, ): boolean { if (typeof pattern === "function") { - return !!pattern(pathToTest, { + return !!endHiddenCallStack(pattern)(pathToTest, { dirname, envName: context.envName, caller: context.caller, @@ -702,8 +717,9 @@ function matchPattern( } if (typeof pathToTest !== "string") { - throw new Error( + throw new ConfigError( `Configuration contains string/RegExp pattern, but no filename was passed to Babel`, + configName, ); } diff --git a/packages/babel-core/src/config/files/configuration.js b/packages/babel-core/src/config/files/configuration.js index f119bc1f8101..85f5f6f2e19c 100644 --- a/packages/babel-core/src/config/files/configuration.js +++ b/packages/babel-core/src/config/files/configuration.js @@ -19,6 +19,9 @@ import type { CallerMetadata } from "../validation/options"; import * as fs from "../../gensync-utils/fs"; import resolve from "../../gensync-utils/resolve"; +import { endHiddenCallStack } from "../../errors/rewrite-stack-trace"; +import ConfigError from "../../errors/config-error"; + const debug = buildDebug("babel:config:loading:files:configuration"); export const ROOT_CONFIG_FILENAMES = [ @@ -113,7 +116,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` + @@ -140,7 +143,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); @@ -194,9 +200,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.", ): mixed); - } catch (err) { - err.message = `${filepath}: Error while loading config - ${err.message}`; - throw err; } finally { LOADING_CONFIGS.delete(filepath); } @@ -204,28 +207,32 @@ const readConfigJS = makeStrongCache(function* readConfigJS( let assertCache = false; if (typeof options === "function") { yield* []; // if we want to make it possible to use async configs - options = ((options: any): (api: PluginAPI) => {})(makeAPI(cache)); + options = endHiddenCallStack(((options: any): (api: PluginAPI) => {}))( + makeAPI(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, ); } 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()) throwCacheError(filepath); return { filepath, @@ -241,7 +248,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 { @@ -257,17 +264,19 @@ const readConfigJSON5 = makeStaticFileCache((filepath, content) => { 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); } return { @@ -286,7 +295,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, + ); } } @@ -299,9 +311,10 @@ const readIgnoreConfig = makeStaticFileCache((filepath, content) => { }; }); -function throwConfigError() { - throw new Error(`\ -Caching was left unconfigured. Babel's plugins, presets, and .babelrc.js files can be configured +function throwCacheError(filepath) { + throw new ConfigError( + `\ +Caching was left unconfigured. Babel's plugins, presets, and configuration files can be configured for various types of caching, using the first param of their handler functions: module.exports = function(api) { @@ -333,5 +346,7 @@ module.exports = function(api) { // Return the value that will be cached. return { }; -};`); +};`, + filepath, + ); } diff --git a/packages/babel-core/src/config/files/module-types.js b/packages/babel-core/src/config/files/module-types.js index 4c761597f9f1..e87f4fedc0ef 100644 --- a/packages/babel-core/src/config/files/module-types.js +++ b/packages/babel-core/src/config/files/module-types.js @@ -3,6 +3,9 @@ import type { Handler } from "gensync"; import path from "path"; import { pathToFileURL } from "url"; +import { endHiddenCallStack } from "../../errors/rewrite-stack-trace"; +import ConfigError from "../../errors/config-error"; + let import_; try { // Node < 13.3 doesn't support import() syntax. @@ -27,7 +30,7 @@ export default function* loadCjsOrMjsDefault( if (yield* isAsync()) { return yield* waitFor(loadMjsDefault(filepath)); } - throw new Error(asyncError); + throw new ConfigError(asyncError); } } @@ -43,21 +46,22 @@ function guessJSModuleType(filename: string): "cjs" | "mjs" | "unknown" { } function loadCjsDefault(filepath: string) { - const module = (require(filepath): mixed); + const module = (endHiddenCallStack(require)(filepath): mixed); // TODO (Babel 8): Remove "undefined" fallback return module?.__esModule ? module.default || undefined : module; } 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; } diff --git a/packages/babel-core/src/config/files/package.js b/packages/babel-core/src/config/files/package.js index 278ddfb852f5..463f6e2cabfb 100644 --- a/packages/babel-core/src/config/files/package.js +++ b/packages/babel-core/src/config/files/package.js @@ -6,6 +6,8 @@ import { makeStaticFileCache } from "./utils"; import type { ConfigFile, FilePackageData } from "./types"; +import ConfigError from "../../errors/config-error"; + const PACKAGE_FILENAME = "package.json"; /** @@ -41,15 +43,20 @@ const readConfigPackage = makeStaticFileCache( try { options = JSON.parse(content); } catch (err) { - err.message = `${filepath}: Error while parsing JSON - ${err.message}`; - throw err; + throw new ConfigError( + `Error while parsing JSON - ${err.message}`, + 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); } return { diff --git a/packages/babel-core/src/config/files/plugins.js b/packages/babel-core/src/config/files/plugins.js index be20468d1615..f56c413466a4 100644 --- a/packages/babel-core/src/config/files/plugins.js +++ b/packages/babel-core/src/config/files/plugins.js @@ -8,6 +8,8 @@ import buildDebug from "debug"; import resolve from "resolve"; import path from "path"; +import ConfigError from "../../errors/config-error"; + const debug = buildDebug("babel:config:loading:files:plugins"); const EXACT_RE = /^module:/; @@ -33,7 +35,7 @@ export function loadPlugin( ): { filepath: string, value: mixed } { const filepath = resolvePlugin(name, dirname); if (!filepath) { - throw new Error(`Plugin ${name} not found relative to ${dirname}`); + throw new ConfigError(`Plugin ${name} not found relative to ${dirname}`); } const value = requireModule("plugin", filepath); diff --git a/packages/babel-core/src/config/validation/options.js b/packages/babel-core/src/config/validation/options.js index c3e7f359cd90..bcafff769678 100644 --- a/packages/babel-core/src/config/validation/options.js +++ b/packages/babel-core/src/config/validation/options.js @@ -29,6 +29,8 @@ import { } from "./option-assertions"; import type { UnloadedDescriptor } from "../config-descriptors"; +import ConfigError from "../../errors/config-error"; + const ROOT_VALIDATORS: ValidatorSet = { cwd: (assertString: Validator<$PropertyType>), root: (assertString: Validator<$PropertyType>), @@ -296,14 +298,22 @@ function getSource(loc: NestingPath): OptionsSource { return loc.type === "root" ? loc.source : getSource(loc.parent); } -export function validate(type: OptionsSource, opts: {}): ValidatedOptions { - return validateNested( - { - type: "root", - source: type, - }, - opts, - ); +export function validate( + type: OptionsSource, + opts: {}, + filename: string, +): ValidatedOptions { + try { + return validateNested( + { + type: "root", + source: type, + }, + opts, + ); + } catch (err) { + throw new ConfigError(err.message, filename); + } } function validateNested(loc: NestingPath, opts: {}) { diff --git a/packages/babel-core/src/errors/config-error.js b/packages/babel-core/src/errors/config-error.js new file mode 100644 index 000000000000..83e76ea35bb2 --- /dev/null +++ b/packages/babel-core/src/errors/config-error.js @@ -0,0 +1,9 @@ +import { injcectVirtualStackFrame, expectedError } from "./rewrite-stack-trace"; + +export default class ConfigError extends Error { + constructor(message, filename) { + super(message); + expectedError(this); + if (filename) injcectVirtualStackFrame(this, filename); + } +} diff --git a/packages/babel-core/src/errors/rewrite-stack-trace.js b/packages/babel-core/src/errors/rewrite-stack-trace.js new file mode 100644 index 000000000000..25918fb4ba0c --- /dev/null +++ b/packages/babel-core/src/errors/rewrite-stack-trace.js @@ -0,0 +1,151 @@ +/** + * This file uses the iternal V8 Stack Trace API (https://v8.dev/docs/stack-trace-api) + * to provide utilities to rewrite the stack trace. + * When this API is not present, all the functions in this file become noops. + * + * beginHiddenCallStack(fn) and endHiddenCallStack(fn) wrap their parameter to + * mark an hidden portion of the stack trace. The function passed to + * beginHiddenCallStack is the first hidden function, while the function passed + * to endHiddenCallStack is the first shown function. + * + * When an error is thrown _outside_ of the hidden zone, everything between + * beginHiddenCallStack and endHiddenCallStack will not be shown. + * If an error is thrown _inside_ the hidden zone, then the whole stack trace + * will be visible: this is to avoid hiding real bugs. + * However, if an error inside the hidden zone is expected, it can be marked + * with the expectedError(error) function to keep the hidden frames hidden. + * + * Consider this call stack (the outer function is the bottom one): + * + * 1. a() + * 2. endHiddenCallStack(b)() + * 3. c() + * 4. beginHiddenCallStack(d)() + * 5. e() + * 6. f() + * + * - If a() throws an error, then its shown call stack will be "a, b, e, f" + * - If b() throws an error, then its shown call stack will be "b, e, f" + * - If c() throws an expected error, then its shown call stack will be "e, f" + * - If c() throws an unexpected error, then its shown call stack will be "c, d, e, f" + * - If d() throws an expected error, then its shown call stack will be "e, f" + * - If d() throws an unexpected error, then its shown call stack will be "d, e, f" + * - If e() throws an error, then its shown call stack will be "e, f" + * + * Additionally, an error can inject additional "virtual" stack frames using the + * injcectVirtualStackFrame(error, filename) function: those are added on the top + * of the existig stack, after hiding the possibly hidden frames. + * In the example above, if we called injcectVirtualStackFrame(error, "h") on the + * expected error thrown by c(), it's shown call stack would have been "h, e, f". + * This can be useful, for example, to report config validation errors as if they + * were directly thrown in the config file. + */ + +const ErrorToString = Function.call.bind(Error.prototype.toString); + +const SUPPORTED = !!Error.captureStackTrace; + +const START_HIDNG = "startHiding - secret - don't use this - v1"; +const STOP_HIDNG = "startHiding - secret - don't use this - v1"; + +const expectedErrors = new WeakSet(); +const virtualFrames = new WeakMap(); + +export function injcectVirtualStackFrame(error, filename) { + if (!SUPPORTED) return; + + let frames = virtualFrames.get(error); + if (!frames) virtualFrames.set(error, (frames = [])); + frames.push({ + // We need __proto__ otherwise it breaks source-map-support's internals + __proto__: { + isNative: () => false, + isConstructor: () => false, + isToplevel: () => true, + getFileName: () => filename, + getLineNumber: () => undefined, + getColumnNumber: () => undefined, + getFunctionName: () => undefined, + getMethodName: () => undefined, + getTypeName: () => undefined, + toString: () => filename, + }, + }); + + return error; +} + +export function expectedError(error) { + if (!SUPPORTED) return; + expectedErrors.add(error); + return error; +} + +export function beginHiddenCallStack(fn) { + if (!SUPPORTED) return fn; + + const stack = getStructuredStackTrace(beginHiddenCallStack); + + return Object.defineProperty( + function() { + setupPrepareStackTrace(stack); + return fn.apply(this, arguments); + }, + "name", + { value: START_HIDNG }, + ); +} + +export function endHiddenCallStack(fn) { + if (!SUPPORTED) return fn; + + return Object.defineProperty( + function() { + return fn.apply(this, arguments); + }, + "name", + { value: STOP_HIDNG }, + ); +} + +function setupPrepareStackTrace(stack) { + const { prepareStackTrace = defaultPrepareStackTrace } = Error; + + Error.prepareStackTrace = function stackTraceRewriter(err, trace) { + const newTrace = []; + let i; + + if (!expectedErrors.has(err)) { + for (i = 0; i < trace.length; i++) { + if (trace[i].getFunctionName() === STOP_HIDNG) break; + newTrace.push(trace[i]); + } + } + + if (virtualFrames.has(err)) newTrace.unshift(...virtualFrames.get(err)); + //newTrace.push("internal Babel functions (elided)"); + newTrace.push(...stack); + + return prepareStackTrace(err, newTrace.slice(0, Error.stackTraceLimit)); + }; +} + +function defaultPrepareStackTrace(err, trace) { + if (trace.length === 0) return ErrorToString(err); + return `${ErrorToString(err)}\n at ${trace.join("\n at ")}`; +} + +function getStructuredStackTrace(firstExcludedFunction) { + const { prepareStackTrace, stackTraceLimit } = Error; + + try { + const o = {}; + Error.stackTraceLimit = Infinity; + Error.prepareStackTrace = (o, structuredStack) => structuredStack; + Error.captureStackTrace(o, firstExcludedFunction); + return o.stack; + } finally { + Error.prepareStackTrace = prepareStackTrace; + Error.stackTraceLimit = stackTraceLimit; + } +} diff --git a/packages/babel-core/src/parse.js b/packages/babel-core/src/parse.js index 3609df763530..c35e26100959 100644 --- a/packages/babel-core/src/parse.js +++ b/packages/babel-core/src/parse.js @@ -7,6 +7,8 @@ import parser from "./parser"; import type { ParseResult } from "./parser"; import normalizeOptions from "./transformation/normalize-opts"; +import { beginHiddenCallStack } from "./errors/rewrite-stack-trace"; + type FileParseCallback = { (Error, null): any, (null, ParseResult | null): any, @@ -41,10 +43,15 @@ export const parse: Parse = (function parse(code, opts, callback) { // For backward-compat with Babel 7's early betas, we allow sync parsing when // no callback is given. Will be dropped in some future Babel major version. - if (callback === undefined) return parseRunner.sync(code, opts); + if (callback === undefined) return parseSync(code, opts); - parseRunner.errback(code, opts, callback); + beginHiddenCallStack(parseRunner.errback)(code, opts, callback); }: Function); -export const parseSync = parseRunner.sync; -export const parseAsync = parseRunner.async; +export function parseSync(...args) { + return beginHiddenCallStack(parseRunner.sync)(...args); +} + +export function parseAsync(...args) { + return beginHiddenCallStack(parseRunner.async)(...args); +} diff --git a/packages/babel-core/test/errors-stacks.js b/packages/babel-core/test/errors-stacks.js new file mode 100644 index 000000000000..9fc4c58b6f07 --- /dev/null +++ b/packages/babel-core/test/errors-stacks.js @@ -0,0 +1,165 @@ +import * as babel from ".."; + +function expectError(run) { + try { + run(); + } catch (e) { + return expect( + e.stack + .split(process.cwd()) + .join("") + // Remove jest-related stack frames + .replace( + /(?:\n\s*at[^\n]+?node_modules\/jest[^\n]+)+/g, + "\n ", + ), + ); + } + throw new Error("It should have thrown an error."); +} + +describe("@babel/core errors", function() { + it("error inside config function", function() { + expectError(() => { + babel.parseSync("foo;", { + root: __dirname + "/fixtures/errors/error-config-function", + }); + }).toMatchInlineSnapshot(` + "Error: Error inside config! + at myConfig (/packages/babel-core/test/fixtures/errors/error-config-function/babel.config.js:2:9) + at Object.parseSync (/packages/babel-core/lib/parse.js:53:54) + at run (/packages/babel-core/test/errors-stacks.js:24:13) + at expectError (/packages/babel-core/test/errors-stacks.js:5:5) + at Object. (/packages/babel-core/test/errors-stacks.js:23:5) + + at new Promise () + " + `); + }); + + it("error inside config function with more frames", function() { + expectError(() => { + babel.parseSync("foo;", { + root: __dirname + "/fixtures/errors/error-config-function-more-frames", + }); + }).toMatchInlineSnapshot(` + "Error: Error inside config! + at f (/packages/babel-core/test/fixtures/errors/error-config-function-more-frames/babel.config.js:6:9) + at g (/packages/babel-core/test/fixtures/errors/error-config-function-more-frames/babel.config.js:10:3) + at myConfig (/packages/babel-core/test/fixtures/errors/error-config-function-more-frames/babel.config.js:2:3) + at Object.parseSync (/packages/babel-core/lib/parse.js:53:54) + at run (/packages/babel-core/test/errors-stacks.js:42:13) + at expectError (/packages/babel-core/test/errors-stacks.js:5:5) + at Object. (/packages/babel-core/test/errors-stacks.js:41:5) + + at new Promise ()" + `); + }); + + it("error inside config file", function() { + expectError(() => { + babel.parseSync("foo;", { + root: __dirname + "/fixtures/errors/error-config-file", + }); + }).toMatchInlineSnapshot(` + "Error: Error inside config! + at Object. (/packages/babel-core/test/fixtures/errors/error-config-file/babel.config.js:4:7) + + at Object.parseSync (/packages/babel-core/lib/parse.js:53:54) + at run (/packages/babel-core/test/errors-stacks.js:61:13) + at expectError (/packages/babel-core/test/errors-stacks.js:5:5) + at Object. (/packages/babel-core/test/errors-stacks.js:60:5) + " + `); + }); + + it("error inside config file with more frames", function() { + expectError(() => { + babel.parseSync("foo;", { + root: __dirname + "/fixtures/errors/error-config-file-more-frames", + }); + }).toMatchInlineSnapshot(` + "Error: Error inside config! + at f (/packages/babel-core/test/fixtures/errors/error-config-file-more-frames/babel.config.js:7:9) + at g (/packages/babel-core/test/fixtures/errors/error-config-file-more-frames/babel.config.js:11:3) + at Object. (/packages/babel-core/test/fixtures/errors/error-config-file-more-frames/babel.config.js:1:90) + + at Object.parseSync (/packages/babel-core/lib/parse.js:53:54) + at run (/packages/babel-core/test/errors-stacks.js:78:13) + at expectError (/packages/babel-core/test/errors-stacks.js:5:5)" + `); + }); + + it("invalid JSON config file", function() { + expectError(() => { + babel.parseSync("foo;", { + root: __dirname + "/fixtures/errors/invalid-json", + }); + }).toMatchInlineSnapshot(` + "Error: Error while parsing config - JSON5: invalid character '}' at 3:1 + at /packages/babel-core/test/fixtures/errors/invalid-json/babel.config.json + at Object.parseSync (/packages/babel-core/lib/parse.js:53:54) + at run (/packages/babel-core/test/errors-stacks.js:24:13) + at expectError (/packages/babel-core/test/errors-stacks.js:5:5) + at Object. (/packages/babel-core/test/errors-stacks.js:23:5) + + at new Promise () + " + `); + }); + + it("use 'exclude' without filename", function() { + expectError(() => { + babel.parseSync("foo;", { + root: __dirname + "/fixtures/errors/use-exclude", + }); + }).toMatchInlineSnapshot(` + "Error: Configuration contains string/RegExp pattern, but no filename was passed to Babel + at /packages/babel-core/test/fixtures/errors/use-exclude/babel.config.js + at Object.parseSync (/packages/babel-core/lib/parse.js:53:54) + at run (/packages/babel-core/test/errors-stacks.js:24:13) + at expectError (/packages/babel-core/test/errors-stacks.js:5:5) + at Object. (/packages/babel-core/test/errors-stacks.js:23:5) + + at new Promise () + " + `); + }); + + it("invalid option", function() { + expectError(() => { + babel.parseSync("foo;", { + root: __dirname + "/fixtures/errors/invalid-option", + }); + }).toMatchInlineSnapshot(` + "Error: .sourceType must be \\"module\\", \\"script\\", \\"unambiguous\\", or undefined + at /packages/babel-core/test/fixtures/errors/invalid-option/babel.config.json + at Object.parseSync (/packages/babel-core/lib/parse.js:53:54) + at run (/packages/babel-core/test/errors-stacks.js:24:13) + at expectError (/packages/babel-core/test/errors-stacks.js:5:5) + at Object. (/packages/babel-core/test/errors-stacks.js:23:5) + + at new Promise () + " + `); + }); + + it("invalid option in programmatic options", function() { + expectError(() => + babel.parseSync("foo;", { + root: __dirname + "/fixtures/errors/valid", + sourceType: "foo", + }), + ).toMatchInlineSnapshot(` + "Error: .sourceType must be \\"module\\", \\"script\\", \\"unambiguous\\", or undefined + at Object.parseSync (/packages/babel-core/lib/parse.js:53:54) + at run (/packages/babel-core/test/errors-stacks.js:24:13) + at expectError (/packages/babel-core/test/errors-stacks.js:5:5) + at Object. (/packages/babel-core/test/errors-stacks.js:23:5) + + at new Promise () + + at processTicksAndRejections (internal/process/task_queues.js:97:5)" + `); + }); +}); diff --git a/packages/babel-core/test/fixtures/errors/error-config-file-more-frames/babel.config.js b/packages/babel-core/test/fixtures/errors/error-config-file-more-frames/babel.config.js new file mode 100644 index 000000000000..924b0e469607 --- /dev/null +++ b/packages/babel-core/test/fixtures/errors/error-config-file-more-frames/babel.config.js @@ -0,0 +1,12 @@ +g(); + +module.exports = function myConfig() { +}; + +function f() { + throw new Error("Error inside config!"); +} + +function g() { + f(); +} diff --git a/packages/babel-core/test/fixtures/errors/error-config-file/babel.config.js b/packages/babel-core/test/fixtures/errors/error-config-file/babel.config.js new file mode 100644 index 000000000000..75f3b90b36d8 --- /dev/null +++ b/packages/babel-core/test/fixtures/errors/error-config-file/babel.config.js @@ -0,0 +1,4 @@ +module.exports = function myConfig() { +}; + +throw new Error("Error inside config!"); diff --git a/packages/babel-core/test/fixtures/errors/error-config-function-more-frames/babel.config.js b/packages/babel-core/test/fixtures/errors/error-config-function-more-frames/babel.config.js new file mode 100644 index 000000000000..2e566443c71f --- /dev/null +++ b/packages/babel-core/test/fixtures/errors/error-config-function-more-frames/babel.config.js @@ -0,0 +1,11 @@ +module.exports = function myConfig() { + g(); +}; + +function f() { + throw new Error("Error inside config!"); +} + +function g() { + f(); +} diff --git a/packages/babel-core/test/fixtures/errors/error-config-function/babel.config.js b/packages/babel-core/test/fixtures/errors/error-config-function/babel.config.js new file mode 100644 index 000000000000..927a5fa8e51c --- /dev/null +++ b/packages/babel-core/test/fixtures/errors/error-config-function/babel.config.js @@ -0,0 +1,3 @@ +module.exports = function myConfig() { + throw new Error("Error inside config!"); +} diff --git a/packages/babel-core/test/fixtures/errors/invalid-json/babel.config.json b/packages/babel-core/test/fixtures/errors/invalid-json/babel.config.json new file mode 100644 index 000000000000..62df75952cde --- /dev/null +++ b/packages/babel-core/test/fixtures/errors/invalid-json/babel.config.json @@ -0,0 +1,3 @@ +{ + foo +} diff --git a/packages/babel-core/test/fixtures/errors/invalid-option/babel.config.json b/packages/babel-core/test/fixtures/errors/invalid-option/babel.config.json new file mode 100644 index 000000000000..5b6f9463c1b8 --- /dev/null +++ b/packages/babel-core/test/fixtures/errors/invalid-option/babel.config.json @@ -0,0 +1,3 @@ +{ + "sourceType": "foo" +} diff --git a/packages/babel-core/test/fixtures/errors/invalid-pkg-json/package.json b/packages/babel-core/test/fixtures/errors/invalid-pkg-json/package.json new file mode 100644 index 000000000000..62df75952cde --- /dev/null +++ b/packages/babel-core/test/fixtures/errors/invalid-pkg-json/package.json @@ -0,0 +1,3 @@ +{ + foo +} diff --git a/packages/babel-core/test/fixtures/errors/use-exclude/babel.config.js b/packages/babel-core/test/fixtures/errors/use-exclude/babel.config.js new file mode 100644 index 000000000000..37e992d37d27 --- /dev/null +++ b/packages/babel-core/test/fixtures/errors/use-exclude/babel.config.js @@ -0,0 +1,4 @@ +module.exports = function myConfig(api) { + api.cache.never(); + return { exclude: /node_modules/ } +}; diff --git a/packages/babel-core/test/fixtures/errors/valid/babel.config.json b/packages/babel-core/test/fixtures/errors/valid/babel.config.json new file mode 100644 index 000000000000..0967ef424bce --- /dev/null +++ b/packages/babel-core/test/fixtures/errors/valid/babel.config.json @@ -0,0 +1 @@ +{}