/
githubmirror
/
babel
Обзор
Документация
Войти
/
githubmirror
/
babel
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
packages/babel-core/test/api.js
1 054 строки
29 KB
liuxingbaoyu
Change jest `snapshotFormat` (#18029)
29 май 2026, 17:41
Не верифицирован
29 май 2026, 17:41
827d003
Код
Авторство
О чём код?
import * as babel from "../lib/index.js"; import { TraceMap, originalPositionFor } from "@jridgewell/trace-mapping"; import path from "node:path"; import generator from "@babel/generator"; import presetEnv from "@babel/preset-env"; import pluginSyntaxFlow from "@babel/plugin-syntax-flow"; import pluginSyntaxJSX from "@babel/plugin-syntax-jsx"; import pluginFlowStripTypes from "@babel/plugin-transform-flow-strip-types"; import { commonJS } from "$repo-utils"; import { itBabel9 } from "$repo-utils"; const { __dirname, require } = commonJS(import.meta.url); const cwd = __dirname; function assertIgnored(result) { expect(result).toBeNull(); } function assertNotIgnored(result) { expect(result).not.toBeNull(); } function parse(code, opts) { return babel.parse(code, { cwd, configFile: false, ...opts }); } function parseSync(code, opts) { return babel.parseSync(code, { cwd, configFile: false, ...opts }); } function transform(code, opts) { return babel.transform(code, { cwd, configFile: false, ...opts }); } function transformSync(code, opts) { return babel.transformSync(code, { cwd, configFile: false, ...opts }); } function transformFile(filename, opts, cb) { return babel.transformFile(filename, { cwd, configFile: false, ...opts }, cb); } function transformFileSync(filename, opts) { return babel.transformFileSync(filename, { cwd, configFile: false, ...opts }); } function transformFileAsync(filename, opts) { return babel.transformFileAsync(filename, { cwd, configFile: false, ...opts, }); } function transformAsync(code, opts) { return babel.transformAsync(code, { cwd, configFile: false, ...opts }); } function transformFromAst(ast, code, opts) { return babel.transformFromAst(ast, code, { cwd, configFile: false, ...opts }); } function transformFromAstSync(ast, code, opts) { return babel.transformFromAstSync(ast, code, { cwd, configFile: false, ...opts, }); } describe("parser and generator options", function () { const recast = { parse: function (code, opts) { return opts.parser.parse(code); }, print: function (ast) { return generator(ast); }, }; function newTransform(string) { return transformSync(string, { ast: true, parserOpts: { parser: recast.parse, plugins: ["flow"], allowImportExportEverywhere: true, }, generatorOpts: { generator: recast.print, }, }); } it("options", function () { const string = "original;"; expect(newTransform(string).ast).toEqual( transformSync(string, { ast: true }).ast, ); expect(newTransform(string).code).toBe(string); }); it("experimental syntax", function () { const experimental = "var a: number = 1;"; expect(newTransform(experimental).ast).toEqual( transformSync(experimental, { ast: true, parserOpts: { plugins: ["flow"], }, }).ast, ); expect(newTransform(experimental).code).toBe(experimental); function newTransformWithPlugins(string) { return transformSync(string, { ast: true, plugins: [pluginSyntaxFlow], parserOpts: { parser: recast.parse, }, generatorOpts: { generator: recast.print, }, }); } expect(newTransformWithPlugins(experimental).ast).toEqual( transformSync(experimental, { ast: true, parserOpts: { plugins: ["flow"], }, }).ast, ); expect(newTransformWithPlugins(experimental).code).toBe(experimental); }); it("other options", function () { const experimental = "if (true) {\n import a from 'a';\n}"; expect(newTransform(experimental).ast).not.toBe( transformSync(experimental, { ast: true, parserOpts: { allowImportExportEverywhere: true, }, }).ast, ); expect(newTransform(experimental).code).toBe(experimental); }); }); describe("api", function () { it("exposes the resolvePlugin method", function () { expect(() => babel.resolvePlugin("nonexistent-plugin")).toThrow( /Cannot (?:find|resolve) module 'babel-plugin-nonexistent-plugin'/, ); }); it("exposes the resolvePreset method", function () { expect(() => babel.resolvePreset("nonexistent-preset")).toThrow( /Cannot (?:find|resolve) module 'babel-preset-nonexistent-preset'/, ); }); it("exposes types", function () { expect(babel.types).toBeDefined(); }); it("exposes the parser's token types", function () { expect(babel.tokTypes).toBeDefined(); }); it("parse throws on undefined callback", () => { expect(() => parse("", {})).toThrowErrorMatchingInlineSnapshot( `"Starting from Babel 8.0.0, the 'parse' function expects a callback. If you need to call it synchronously, please use 'parseSync'."`, ); }); it("transform throws on undefined callback", () => { const options = { filename: "example.js", }; expect(() => transform("", options)).toThrowErrorMatchingInlineSnapshot( `"Starting from Babel 8.0.0, the 'transform' function expects a callback. If you need to call it synchronously, please use 'transformSync'."`, ); }); it("transformFile", function () { const options = { babelrc: false, }; Object.freeze(options); return new Promise((resolve, reject) => { transformFile( cwd + "/fixtures/api/file.js", options, function (err, res) { if (err) return reject(err); expect(res.code).toBe("foo();"); // keep user options untouched expect(options).toEqual({ babelrc: false }); resolve(); }, ); }); }); it("transformFileAsync", async function () { const options = { babelrc: false, }; Object.freeze(options); const res = await transformFileAsync( cwd + "/fixtures/api/file.js", options, ); expect(res.code).toBe("foo();"); // keep user options untouched expect(options).toEqual({ babelrc: false }); }); it("transformFile throws on undefined callback", () => { const options = { babelrc: false, }; expect(() => transformFile(cwd + "/fixtures/api/file.js", options), ).toThrowErrorMatchingInlineSnapshot( `"Asynchronous function called without callback"`, ); }); it("transformFileSync", function () { const options = { babelrc: false, }; Object.freeze(options); expect(transformFileSync(cwd + "/fixtures/api/file.js", options).code).toBe( "foo();", ); expect(options).toEqual({ babelrc: false }); }); it("transformFromAst throws on undefined callback", () => { const program = "const identifier = 1"; const node = parseSync(program); expect(() => transformFromAst(node, program), ).toThrowErrorMatchingInlineSnapshot( `"Starting from Babel 8.0.0, the 'transformFromAst' function expects a callback. If you need to call it synchronously, please use 'transformFromAstSync'."`, ); }); it("transformFromAst should generate same code with different cloneInputAst", function () { const program = `//test1 /*test2*/var/*test3*/ a = 1/*test4*/;//test5 //test6 var b; `; const node = parseSync(program); const { code } = transformFromAstSync(node, program, { plugins: [ function () { return { visitor: { Identifier: function (path) { path.node.name = "replaced"; }, }, }; }, ], }); const { code: code2 } = transformFromAstSync(node, program, { cloneInputAst: false, plugins: [ function () { return { visitor: { Identifier: function (path) { path.node.name = "replaced"; }, }, }; }, ], }); expect(code).toBe(code2); }); it("transformFromAstSync should not cause infinite recursion with circular objects", () => { const program = "const identifier = 1"; const node = parseSync(program); node.program.body[0].extra = { parent: node.program }; expect(transformFromAstSync(node, program, {}).code).toBe( "const identifier = 1;", ); }); it("transformFromAst should not mutate the AST", function () { const program = "const identifier = 1"; const node = parseSync(program); const { code } = transformFromAstSync(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("transformFromAstSync should mutate the AST when cloneInputAst is false", function () { const program = "const identifier = 1"; const node = parseSync(program); const { code } = transformFromAstSync(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 () { transformSync("", { plugins: [pluginSyntaxJSX, false], }); }).toThrow(/.plugins\[1\] must be a string, object, function/); }); it("options merge backwards", async function () { const result = await transformAsync("", { cwd, presets: [ () => ({ plugins: [() => ({ name: "plugin-4" }), () => ({ name: "plugin-5" })], }), () => ({ plugins: [() => ({ name: "plugin-2" }), () => ({ name: "plugin-3" })], }), ], plugins: [() => ({ name: "plugin-0" }), () => ({ name: "plugin-1" })], }); expect(result.options.plugins[0].key).toBe("plugin-0"); expect(result.options.plugins[1].key).toBe("plugin-1"); expect(result.options.plugins[2].key).toBe("plugin-2"); expect(result.options.plugins[3].key).toBe("plugin-3"); expect(result.options.plugins[4].key).toBe("plugin-4"); expect(result.options.plugins[5].key).toBe("plugin-5"); }); it("option wrapPluginVisitorMethod", function () { let calledRaw = 0; let calledIntercept = 0; transformSync("function foo() { bar(foobar); }", { wrapPluginVisitorMethod: function (pluginAlias, visitorType, callback) { if (pluginAlias !== "foobar") { return callback; } expect(visitorType).toBe("enter"); return function () { calledIntercept++; return callback.apply(this, arguments); }; }, plugins: [ () => ({ name: "foobar", visitor: { "Program|Identifier": function () { calledRaw++; }, }, }), ], }); expect(calledRaw).toBe(4); expect(calledIntercept).toBe(4); }); it("pass per preset", function () { let aliasBaseType = null; function execTest(passPerPreset) { return transformSync("type Foo = number; let x = (y): Foo => y;", { sourceType: "script", passPerPreset: passPerPreset, presets: [ // First preset with our plugin, "before" function () { return { plugins: [ () => ({ visitor: { Function: function (path) { const alias = path.scope .getProgramParent() .path.get("body")[0].node; if (!babel.types.isTypeAlias(alias)) return; // In case of `passPerPreset` being `false`, the // alias node is already removed by Flow plugin. if (!alias) { return; } // In case of `passPerPreset` being `true`, the // alias node should still exist. aliasBaseType = alias.right.type; // NumberTypeAnnotation }, }, }), ], }; }, // env preset [presetEnv, { targets: { browsers: "ie 6" } }], // Third preset for Flow. () => ({ plugins: [pluginSyntaxFlow, pluginFlowStripTypes], }), ], }); } // 1. passPerPreset: true let result = execTest(true); expect(aliasBaseType).toBe("NumberTypeAnnotation"); expect(result.code).toBe("var x = function x(y) {\n return y;\n};"); // 2. passPerPreset: false aliasBaseType = null; result = execTest(false); expect(aliasBaseType).toBeNull(); expect(result.code).toBe("var x = function x(y) {\n return y;\n};"); }); it("complex plugin and preset ordering", function () { function pushPlugin(str) { return { visitor: { Program(path) { path.pushContainer( "body", babel.types.expressionStatement(babel.types.identifier(str)), ); }, }, }; } function pushPreset(str) { return { plugins: [pushPlugin(str)] }; } const oldEnv = process.env.BABEL_ENV; process.env.BABEL_ENV = "development"; const result = transformSync("", { cwd: path.join(cwd, "fixtures", "config", "complex-plugin-config"), filename: path.join( cwd, "fixtures", "config", "complex-plugin-config", "file.js", ), presets: [pushPreset("argone"), pushPreset("argtwo")], env: { development: { passPerPreset: true, presets: [pushPreset("argthree"), pushPreset("argfour")], }, }, }); if (oldEnv === undefined) { delete process.env.BABEL_ENV; } else { process.env.BABEL_ENV = oldEnv; } expect(result.code).toBe( [ "thirteen;", "fourteen;", "seventeen;", "eighteen;", "one;", "two;", "eleven;", "twelve;", "argtwo;", "argone;", "five;", "six;", "twentyone;", "twentytwo;", "three;", "four;", "nineteen;", "twenty;", "fifteen;", "sixteen;", "seven;", "eight;", "nine;", "ten;", "argthree;", "argfour;", ].join("\n"), ); }); it("interpreter directive backward-compat", function () { function doTransform(code, preHandler) { return transformSync(code, { plugins: [ { pre: preHandler, }, ], }).code; } // Writes value properly. expect( doTransform("", file => { file.shebang = "env node"; }), ).toBe(`#!env node`); expect( doTransform("#!env node", file => { file.shebang = "env node2"; }), ).toBe(`#!env node2`); expect( doTransform("", file => { file.shebang = ""; }), ).toBe(``); expect( doTransform("#!env node", file => { file.shebang = ""; }), ).toBe(``); // Reads value properly. doTransform("", file => { expect(file.shebang).toBe(""); }); doTransform("#!env node", file => { expect(file.shebang).toBe("env node"); }); // Reads and writes properly. expect( doTransform("#!env node", file => { expect(file.shebang).toBe("env node"); file.shebang = "env node2"; expect(file.shebang).toBe("env node2"); file.shebang = "env node3"; }), ).toBe(`#!env node3`); }); it("source map merging", function () { const result = transformSync( [ 'function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }', "", "let Foo = function Foo() {", " _classCallCheck(this, Foo);", "};", "", "//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInN0ZG91dCJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztJQUFNLEdBQUcsWUFBSCxHQUFHO3dCQUFILEdBQUciLCJmaWxlIjoidW5kZWZpbmVkIiwic291cmNlc0NvbnRlbnQiOlsiY2xhc3MgRm9vIHt9XG4iXX0=", ].join("\n"), { sourceMap: true, }, ); expect( [ "function _classCallCheck(instance, Constructor) {", " if (!(instance instanceof Constructor)) {", ' throw new TypeError("Cannot call a class as a function");', " }", "}", "let Foo = function Foo() {", " _classCallCheck(this, Foo);", "};", ].join("\n"), ).toBe(result.code); const consumer = new TraceMap(result.map); expect( originalPositionFor(consumer, { line: 6, column: 4, }), ).toEqual({ name: null, source: "stdout", line: 1, column: 6, }); }); it("default source map filename", function () { return transformAsync("var a = 10;", { cwd: "/some/absolute", filename: "/some/absolute/file/path.js", sourceMaps: true, }).then(function (result) { expect(result.map.sources).toEqual(["path.js"]); }); }); it("code option false", function () { return transformAsync("foo('bar');", { code: false }).then( function (result) { expect(result.code).toBeFalsy(); }, ); }); it("ast option false", function () { return transformAsync("foo('bar');", { ast: false }).then( function (result) { expect(result.ast).toBeFalsy(); }, ); }); it("ast option true", function () { return transformAsync("foo('bar');", { ast: true }).then(function (result) { expect(result.ast).toBeTruthy(); }); }); it("ast option default", function () { return transformAsync("foo('bar');").then(function (result) { expect(result.ast).toBeFalsy(); }); }); it("auxiliaryComment option", function () { return transformAsync("class Foo {}", { auxiliaryCommentBefore: "before", auxiliaryCommentAfter: "after", plugins: [ function (babel) { const t = babel.types; return { visitor: { Program: function (path) { path.unshiftContainer( "body", t.expressionStatement(t.identifier("start")), ); path.pushContainer( "body", t.expressionStatement(t.identifier("end")), ); }, }, }; }, ], }).then(function (result) { expect(result.code).toBe( "/*before*/\nstart;\n/*after*/\nclass Foo {}\n/*before*/\nend;\n/*after*/", ); }); }); it("ignore option", function () { return Promise.all([ transformAsync("", { ignore: ["/foo"], filename: "/foo/node_modules/bar", }).then(assertIgnored), transformAsync("", { ignore: ["/foo/node_modules"], filename: "/foo/node_modules/bar", }).then(assertIgnored), transformAsync("", { ignore: ["/foo/node_modules/*"], filename: "/foo/node_modules/bar", }).then(assertIgnored), transformAsync("", { ignore: ["/foo/**/*"], filename: "/foo/node_modules/bar", }).then(assertIgnored), transformAsync("", { ignore: ["/foo/node_modules/*.bar"], filename: "/foo/node_modules/foo.bar", }).then(assertIgnored), transformAsync("", { ignore: ["/foo/node_modules/*.foo"], filename: "/foo/node_modules/foo.bar", }).then(assertNotIgnored), transformAsync("", { ignore: ["/bar/**/*"], filename: "/foo/node_modules/foo.bar", }).then(assertNotIgnored), ]); }); it("only option", function () { return Promise.all([ transformAsync("", { only: ["/foo"], filename: "/foo/node_modules/bar", }).then(assertNotIgnored), transformAsync("", { only: ["/foo/*"], filename: "/foo/node_modules/bar", }).then(assertNotIgnored), transformAsync("", { only: ["/foo/node_modules"], filename: "/foo/node_modules/bar", }).then(assertNotIgnored), transformAsync("", { only: ["/foo/node_modules/*.bar"], filename: "/foo/node_modules/foo.bar", }).then(assertNotIgnored), transformAsync("", { only: ["/foo/node_modules"], filename: "/foo/node_module/bar", }).then(assertIgnored), transformAsync("", { only: ["/foo/node_modules"], filename: "/bar/node_modules/foo", }).then(assertIgnored), transformAsync("", { only: ["/foo/node_modules/*.bar"], filename: "/foo/node_modules/bar.foo", }).then(assertIgnored), ]); }); describe("env option", function () { const oldBabelEnv = process.env.BABEL_ENV; const oldNodeEnv = process.env.NODE_ENV; beforeEach(function () { // Tests need to run with the default and specific values for these. They // need to be cleared for each test. delete process.env.BABEL_ENV; delete process.env.NODE_ENV; }); afterAll(function () { process.env.BABEL_ENV = oldBabelEnv; process.env.NODE_ENV = oldNodeEnv; }); it("default", function () { const result = transformSync("foo;", { env: { development: { comments: false }, }, }); expect(result.options.comments).toBe(false); }); it("BABEL_ENV", function () { process.env.BABEL_ENV = "foo"; const result = transformSync("foo;", { env: { foo: { comments: false }, }, }); expect(result.options.comments).toBe(false); }); it("NODE_ENV", function () { process.env.NODE_ENV = "foo"; const result = transformSync("foo;", { env: { foo: { comments: false }, }, }); expect(result.options.comments).toBe(false); }); }); describe("buildExternalHelpers", function () { it("all", function () { const script = babel.buildExternalHelpers(); expect(script).toEqual(expect.stringContaining("classCallCheck")); expect(script).toEqual(expect.stringContaining("inherits")); }); it("allowlist", function () { const script = babel.buildExternalHelpers(["inherits"]); expect(script).not.toEqual(expect.stringContaining("classCallCheck")); expect(script).toEqual(expect.stringContaining("inherits")); }); it("empty allowlist", function () { const script = babel.buildExternalHelpers([]); expect(script).not.toEqual(expect.stringContaining("classCallCheck")); expect(script).not.toEqual(expect.stringContaining("inherits")); }); it("underscored", function () { const script = babel.buildExternalHelpers(["typeof"]); expect(script).toEqual(expect.stringContaining("typeof")); }); describe("output types", function () { it("global", function () { const script = babel.buildExternalHelpers(["get"], "global"); expect(script).toMatchInlineSnapshot(` "(function (global) { var babelHelpers = global.babelHelpers = {}; function _get() { return babelHelpers.get = _get = "undefined" != typeof Reflect && Reflect.get ? Reflect.get.bind() : function (e, t, r) { var p = babelHelpers.superPropBase(e, t); if (p) { var n = Object.getOwnPropertyDescriptor(p, t); return n.get ? n.get.call(arguments.length < 3 ? e : r) : n.value; } }, _get.apply(null, arguments); } babelHelpers.get = _get; })(typeof global === "undefined" ? self : global);" `); }); it("umd", function () { const script = babel.buildExternalHelpers(["get"], "umd"); expect(script).toMatchInlineSnapshot(` "(function (root, factory) { if (typeof define === "function" && define.amd) { define(["exports"], factory); } else if (typeof exports === "object") { factory(exports); } else { factory(root.babelHelpers = {}); } })(this, function (global) { var babelHelpers = global; function _get() { return babelHelpers.get = _get = "undefined" != typeof Reflect && Reflect.get ? Reflect.get.bind() : function (e, t, r) { var p = babelHelpers.superPropBase(e, t); if (p) { var n = Object.getOwnPropertyDescriptor(p, t); return n.get ? n.get.call(arguments.length < 3 ? e : r) : n.value; } }, _get.apply(null, arguments); } babelHelpers.get = _get; });" `); }); it("var", function () { const script = babel.buildExternalHelpers(["get"], "var"); expect(script).toMatchInlineSnapshot(` "var babelHelpers = {}; function _get() { return babelHelpers.get = _get = "undefined" != typeof Reflect && Reflect.get ? Reflect.get.bind() : function (e, t, r) { var p = babelHelpers.superPropBase(e, t); if (p) { var n = Object.getOwnPropertyDescriptor(p, t); return n.get ? n.get.call(arguments.length < 3 ? e : r) : n.value; } }, _get.apply(null, arguments); } babelHelpers.get = _get; babelHelpers;" `); }); it("module", function () { const script = babel.buildExternalHelpers(["get"], "module"); expect(script).toMatchInlineSnapshot(` "export { _get as get }; function _get() { return _get = "undefined" != typeof Reflect && Reflect.get ? Reflect.get.bind() : function (e, t, r) { var p = _superPropBase(e, t); if (p) { var n = Object.getOwnPropertyDescriptor(p, t); return n.get ? n.get.call(arguments.length < 3 ? e : r) : n.value; } }, _get.apply(null, arguments); }" `); }); }); }); describe("handle parsing errors", function () { const options = { babelrc: false, }; it("only syntax plugin available", function () { return new Promise(resolve => { transformFile( cwd + "/fixtures/api/parsing-errors/only-syntax/file.js", options, function (err) { expect(err.message).toMatch( "Support for the experimental syntax 'pipelineOperator' isn't currently enabled (1:3):", ); expect(err.message).toMatch( "Add @babel/plugin-proposal-pipeline-operator (https://github.com/babel/babel/tree/main/packages/babel-plugin-proposal-pipeline-operator) to the " + "'plugins' section of your Babel config to enable transformation.", ); resolve(); }, ); }); }); it("both syntax and transform plugin available", async function () { const promise = new Promise((resolve, reject) => { transformFile( cwd + "/fixtures/api/parsing-errors/syntax-and-transform/file.js", options, (err, result) => (err ? reject(err) : resolve(result)), ); }); await expect(promise).rejects.toThrow(); const err = await promise.catch(e => e); expect(err.message).toMatch( "Support for the experimental syntax 'doExpressions' isn't currently enabled (1:2):", ); expect(err.message).toMatch( "Add @babel/plugin-proposal-do-expressions (https://github.com/babel/babel/tree/main/packages/babel-plugin-proposal-do-expressions) to the " + "'plugins' section of your Babel config to enable transformation.", ); expect(err.message).toMatch( /npx cross-env BABEL_SHOW_CONFIG_FOR=(.*?)[\\/]parsing-errors[\\/]syntax-and-transform[\\/]file.js/, ); }); }); describe("missing helpers", function () { it("should always throw", function () { expect(() => babel.transformSync(``, { configFile: false, plugins: [ function () { return { visitor: { Program(path) { try { path.pushContainer("body", this.addHelper("fooBar")); } catch {} path.pushContainer("body", this.addHelper("fooBar")); }, }, }; }, ], }), ).toThrowErrorMatchingInlineSnapshot( `"unknown file: Unknown helper fooBar"`, ); }); }); it("exports the version", function () { expect(babel.version[0]).toBe("8"); }); itBabel9("the version ends with 999999999", () => { expect(babel.version.endsWith("999999999")).toBe(true); }); }); describe("cjs-proxy", function () { it("error should be caught", () => { let err; try { const cjs = require("../lib/index.js"); cjs.parse("foo"); } catch (error) { err = error; } expect(err).toBeInstanceOf(Error); }); });