diff --git a/packages/babel-core/src/transformation/normalize-file.js b/packages/babel-core/src/transformation/normalize-file.js index 203488acd6..38a015f1ac 100644 --- a/packages/babel-core/src/transformation/normalize-file.js +++ b/packages/babel-core/src/transformation/normalize-file.js @@ -2,6 +2,7 @@ import path from "path"; import buildDebug from "debug"; +import cloneDeep from "lodash/cloneDeep"; import * as t from "@babel/types"; import type { PluginPasses } from "../config"; import convertSourceMap, { typeof Converter } from "convert-source-map"; @@ -75,6 +76,7 @@ export default function normalizeFile( } else if (ast.type !== "File") { throw new Error("AST root must be a Program or File node"); } + ast = cloneDeep(ast); } else { // The parser's AST types aren't fully compatible with the types generated // by the logic in babel-types. diff --git a/packages/babel-core/test/api.js b/packages/babel-core/test/api.js index a3b4568a7a..5c3a97c7a3 100644 --- a/packages/babel-core/test/api.js +++ b/packages/babel-core/test/api.js @@ -12,6 +12,13 @@ function assertNotIgnored(result) { expect(result).not.toBeNull(); } +function parse(code, opts) { + return babel.parse(code, { + cwd: __dirname, + ...opts, + }); +} + function transform(code, opts) { return babel.transform(code, { cwd: __dirname, @@ -43,6 +50,13 @@ function transformAsync(code, opts) { }); } +function transformFromAst(ast, code, opts) { + return babel.transformFromAst(ast, code, { + cwd: __dirname, + ...opts, + }); +} + describe("parser and generator options", function() { const recast = { parse: function(code, opts) { @@ -168,6 +182,30 @@ describe("api", function() { expect(options).toEqual({ babelrc: false }); }); + it("transformFromAst should not mutate the AST", function() { + const program = "const identifier = 1"; + const node = parse(program); + const { code } = transformFromAst(node, program, { + 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( + "identifier", + "original ast should not have been mutated", + ); + }); + it("options throw on falsy true", function() { return expect(function() { transform("", {