diff --git a/packages/babel-core/src/config/partial.js b/packages/babel-core/src/config/partial.js index 4d8defe4b664..aa540781496d 100644 --- a/packages/babel-core/src/config/partial.js +++ b/packages/babel-core/src/config/partial.js @@ -81,6 +81,7 @@ export default function* loadPrivatePartialConfig( root: rootDir = ".", rootMode = "root", caller, + cloneInputAst = true, } = args; const absoluteCwd = path.resolve(cwd); const absoluteRootDir = yield* resolveRootMode( @@ -110,6 +111,7 @@ export default function* loadPrivatePartialConfig( // 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. + options.cloneInputAst = cloneInputAst; options.babelrc = false; options.configFile = false; options.passPerPreset = false; diff --git a/packages/babel-core/src/config/validation/options.js b/packages/babel-core/src/config/validation/options.js index d4500a747c26..df09c90deab2 100644 --- a/packages/babel-core/src/config/validation/options.js +++ b/packages/babel-core/src/config/validation/options.js @@ -51,6 +51,10 @@ const ROOT_VALIDATORS: ValidatorSet = { code: (assertBoolean: Validator<$PropertyType>), ast: (assertBoolean: Validator<$PropertyType>), + cloneInputAst: (assertBoolean: Validator< + $PropertyType, + >), + envName: (assertString: Validator< $PropertyType, >), @@ -184,6 +188,7 @@ export type ValidatedOptions = { rootMode?: RootMode, code?: boolean, ast?: boolean, + cloneInputAst?: boolean, inputSourceMap?: RootInputSourceMapOption, envName?: string, caller?: CallerMetadata, diff --git a/packages/babel-core/src/transformation/normalize-file.js b/packages/babel-core/src/transformation/normalize-file.js index 69afea4f9fd6..aca84e1e4e95 100644 --- a/packages/babel-core/src/transformation/normalize-file.js +++ b/packages/babel-core/src/transformation/normalize-file.js @@ -34,7 +34,11 @@ export default function* normalizeFile( } else if (ast.type !== "File") { throw new Error("AST root must be a Program or File node"); } - ast = cloneDeep(ast); + + const { cloneInputAst } = options; + if (cloneInputAst) { + ast = cloneDeep(ast); + } } else { ast = yield* parser(pluginPasses, options, code); } diff --git a/packages/babel-core/test/api.js b/packages/babel-core/test/api.js index 8da161d59556..ab79e74504fa 100644 --- a/packages/babel-core/test/api.js +++ b/packages/babel-core/test/api.js @@ -214,6 +214,31 @@ describe("api", function () { ); }); + it("transformFromAst should mutate the AST when cloneInputAst is false", function () { + const program = "const identifier = 1"; + const node = parse(program); + const { code } = transformFromAst(node, program, { + cloneInputAst: false, + plugins: [ + function () { + return { + visitor: { + Identifier: function (path) { + path.node.name = "replaced"; + }, + }, + }; + }, + ], + }); + + expect(code).toBe("const replaced = 1;"); + expect(node.program.body[0].declarations[0].id.name).toBe( + "replaced", + "original ast should have been mutated", + ); + }); + it("options throw on falsy true", function () { return expect(function () { transform("", { diff --git a/packages/babel-core/test/config-chain.js b/packages/babel-core/test/config-chain.js index c0b2e512c43f..d78bd6a3fc4a 100644 --- a/packages/babel-core/test/config-chain.js +++ b/packages/babel-core/test/config-chain.js @@ -984,6 +984,7 @@ describe("buildConfigChain", function () { passPerPreset: false, plugins: [], presets: [], + cloneInputAst: true, }); const realEnv = process.env.NODE_ENV; const realBabelEnv = process.env.BABEL_ENV;