Skip to content

Commit

Permalink
More misc Babel 8 little changes (#15550)
Browse files Browse the repository at this point in the history
  • Loading branch information
nicolo-ribaudo committed Apr 19, 2023
1 parent 1849374 commit f3e9aee
Show file tree
Hide file tree
Showing 29 changed files with 478 additions and 243 deletions.
11 changes: 10 additions & 1 deletion packages/babel-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,16 @@
},
"conditions": {
"BABEL_8_BREAKING": [
null,
{
"peerDependencies": {
"@babel/preset-typescript": "^7.21.4 || ^8.0.0-0"
},
"peerDependenciesMeta": {
"@babel/preset-typescript": {
"optional": true
}
}
},
{
"exports": null
}
Expand Down
23 changes: 13 additions & 10 deletions packages/babel-core/src/config/files/module-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,10 +124,13 @@ function loadCtsDefault(filepath: string) {

function loadCjsDefault(filepath: string, fallbackToTranspiledModule: boolean) {
const module = endHiddenCallStack(require)(filepath);
return module?.__esModule
? // TODO(Babel 8): Remove "module" and "undefined" fallback
module.default || (fallbackToTranspiledModule ? module : undefined)
: module;
if (process.env.BABEL_8_BREAKING) {
return module?.__esModule ? module.default : module;
} else {
return module?.__esModule
? module.default || (fallbackToTranspiledModule ? module : undefined)
: module;
}
}

async function loadMjsDefault(filepath: string) {
Expand All @@ -154,19 +157,19 @@ function getTSPreset(filepath: string) {
let message =
"You appear to be using a .cts file as Babel configuration, but the `@babel/preset-typescript` package was not found: please install it!";

if (process.versions.pnp) {
// Using Yarn PnP, which doesn't allow requiring packages that are not
// explicitly specified as dependencies.
// TODO(Babel 8): Explicitly add `@babel/preset-typescript` as an
// optional peer dependency of `@babel/core`.
message += `
if (!process.env.BABEL_8_BREAKING) {
if (process.versions.pnp) {
// Using Yarn PnP, which doesn't allow requiring packages that are not
// explicitly specified as dependencies.
message += `
If you are using Yarn Plug'n'Play, you may also need to add the following configuration to your .yarnrc.yml file:
packageExtensions:
\t"@babel/core@*":
\t\tpeerDependencies:
\t\t\t"@babel/preset-typescript": "*"
`;
}
}

throw new ConfigError(message, filepath);
Expand Down
38 changes: 21 additions & 17 deletions packages/babel-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,10 @@ export {
loadPartialConfigSync,
loadPartialConfigAsync,
loadOptions,
loadOptionsSync,
loadOptionsAsync,
} from "./config";
import { loadOptionsSync } from "./config";
export { loadOptionsSync };

export type {
CallerMetadata,
Expand Down Expand Up @@ -73,22 +74,6 @@ export const DEFAULT_EXTENSIONS = Object.freeze([
".cjs",
] as const);

// For easier backward-compatibility, provide an API like the one we exposed in Babel 6.
// TODO(Babel 8): Remove this.
import { loadOptionsSync } from "./config";
export class OptionManager {
init(opts: {}) {
return loadOptionsSync(opts);
}
}

// TODO(Babel 8): Remove this.
export function Plugin(alias: string) {
throw new Error(
`The (${alias}) Babel 5 plugin is being run with an unsupported Babel version.`,
);
}

import Module from "module";
import * as thisFile from "./index";
if (USE_ESM) {
Expand All @@ -98,3 +83,22 @@ if (USE_ESM) {
cjsProxy["__ initialize @babel/core cjs proxy __"] = thisFile;
}
}

if (!process.env.BABEL_8_BREAKING) {
// For easier backward-compatibility, provide an API like the one we exposed in Babel 6.
if (!USE_ESM) {
// eslint-disable-next-line no-restricted-globals
exports.OptionManager = class OptionManager {
init(opts: {}) {
return loadOptionsSync(opts);
}
};

// eslint-disable-next-line no-restricted-globals
exports.Plugin = function Plugin(alias: string) {
throw new Error(
`The (${alias}) Babel 5 plugin is being run with an unsupported Babel version.`,
);
};
}
}
38 changes: 30 additions & 8 deletions packages/babel-core/test/option-manager.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as babel from "../lib/index.js";
import path from "path";
import { fileURLToPath } from "url";
import { readFileSync } from "fs";

const cwd = path.dirname(fileURLToPath(import.meta.url));

Expand All @@ -12,8 +13,20 @@ function loadOptionsAsync(opts) {
return babel.loadOptionsAsync({ cwd, ...opts });
}

let USE_ESM = false;
try {
const type = readFileSync(
new URL("../../../.module-type", import.meta.url),
"utf-8",
).trim();
USE_ESM = type === "module";
} catch {}

const itBabel7 = process.env.BABEL_8_BREAKING ? it.skip : it;
const itBabel7cjs = process.env.BABEL_8_BREAKING || USE_ESM ? it.skip : it;

describe("option-manager", () => {
it("throws for babel 5 plugin", () => {
itBabel7cjs("throws for babel 5 plugin", () => {
return expect(() => {
loadOptions({
plugins: [({ Plugin }) => new Plugin("object-assign", {})],
Expand Down Expand Up @@ -248,16 +261,25 @@ describe("option-manager", () => {
expect(options.presets).toHaveLength(0);
});

it.each([
["es2015_named", /Must export a default export when using ES6 modules/],
["es2015_invalid", /Unsupported format: string/],
["es5_invalid", /Unsupported format: string/],
])("%p should throw %p", async (name, msg) => {
itBabel7("es2015_named shuold throw", async () => {
await expect(
loadOptionsAsync({
presets: [path.join(cwd, "fixtures/option-manager/presets", name)],
presets: [
path.join(cwd, "fixtures/option-manager/presets", "es2015_named"),
],
}),
).rejects.toThrow(msg);
).rejects.toThrow(/Must export a default export when using ES6 modules/);
});

it.each(["es2015_invalid", "es5_invalid"])(
"%p should throw",
async name => {
await expect(
loadOptionsAsync({
presets: [path.join(cwd, "fixtures/option-manager/presets", name)],
}),
).rejects.toThrow(/Unsupported format: string/);
},
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@
},
"main": "./lib/index.js",
"dependencies": {
"@babel/helper-explode-assignable-expression": "workspace:^",
"@babel/types": "workspace:^"
},
"devDependencies": {
"@babel/traverse": "workspace:^"
},
"engines": {
"node": ">=6.9.0"
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import type { Scope } from "@babel/traverse";
import {
assignmentExpression,
cloneNode,
isIdentifier,
isLiteral,
isMemberExpression,
isPrivateName,
isPureish,
isSuper,
memberExpression,
toComputedKey,
} from "@babel/types";
import type * as t from "@babel/types";

function getObjRef(
node: t.Identifier | t.MemberExpression,
nodes: Array<t.AssignmentExpression>,
scope: Scope,
): t.Identifier | t.Super {
let ref;
if (isIdentifier(node)) {
if (scope.hasBinding(node.name)) {
// this variable is declared in scope so we can be 100% sure
// that evaluating it multiple times wont trigger a getter
// or something else
return node;
} else {
// could possibly trigger a getter so we need to only evaluate
// it once
ref = node;
}
} else if (isMemberExpression(node)) {
ref = node.object;

if (isSuper(ref) || (isIdentifier(ref) && scope.hasBinding(ref.name))) {
// the object reference that we need to save is locally declared
// so as per the previous comment we can be 100% sure evaluating
// it multiple times will be safe
// Super cannot be directly assigned so lets return it also
return ref;
}
} else {
throw new Error(`We can't explode this node type ${node["type"]}`);
}

const temp = scope.generateUidIdentifierBasedOnNode(ref);
scope.push({ id: temp });
nodes.push(assignmentExpression("=", cloneNode(temp), cloneNode(ref)));
return temp;
}

function getPropRef(
node: t.MemberExpression,
nodes: Array<t.AssignmentExpression>,
scope: Scope,
): t.Identifier | t.Literal {
const prop = node.property;
if (isPrivateName(prop)) {
throw new Error(
"We can't generate property ref for private name, please install `@babel/plugin-proposal-class-properties`",
);
}
const key = toComputedKey(node, prop);
if (isLiteral(key) && isPureish(key)) return key;

const temp = scope.generateUidIdentifierBasedOnNode(prop);
scope.push({ id: temp });
nodes.push(assignmentExpression("=", cloneNode(temp), cloneNode(prop)));
return temp;
}

export default function explode(
node: t.Identifier | t.MemberExpression,
nodes: Array<t.AssignmentExpression>,
scope: Scope,
): {
uid: t.Identifier | t.MemberExpression | t.Super;
ref: t.Identifier | t.MemberExpression;
} {
const obj = getObjRef(node, nodes, scope);

let ref, uid;

if (isIdentifier(node)) {
ref = cloneNode(node);
uid = obj;
} else {
const prop = getPropRef(node, nodes, scope);
const computed = node.computed || isLiteral(prop);
uid = memberExpression(cloneNode(obj), cloneNode(prop), computed);
ref = memberExpression(cloneNode(obj), cloneNode(prop), computed);
}

return {
uid: uid,
ref: ref,
};
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import explode from "@babel/helper-explode-assignable-expression";
import { assignmentExpression, sequenceExpression } from "@babel/types";
import type { Visitor } from "@babel/traverse";
import type * as t from "@babel/types";

import explode from "./explode-assignable-expression";

export default function (opts: {
build: (
left: t.Expression | t.PrivateName | t.Super,
Expand All @@ -19,7 +20,7 @@ export default function (opts: {

const nodes: t.AssignmentExpression[] = [];
// @ts-expect-error Fixme: node.left can be a TSAsExpression
const exploded = explode(node.left, nodes, this, scope);
const exploded = explode(node.left, nodes, scope);
nodes.push(
assignmentExpression(
"=",
Expand Down
27 changes: 18 additions & 9 deletions packages/babel-helper-create-class-features-plugin/src/features.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,24 @@ import type { File, types as t } from "@babel/core";
import type { NodePath } from "@babel/traverse";
import { hasOwnDecorators } from "./decorators";

export const FEATURES = Object.freeze({
//classes: 1 << 0,
fields: 1 << 1,
privateMethods: 1 << 2,
// TODO(Babel 8): Remove this
decorators: 1 << 3,
privateIn: 1 << 4,
staticBlocks: 1 << 5,
});
export const FEATURES = Object.freeze(
process.env.BABEL_8_BREAKING
? {
//classes: 1 << 0,
fields: 1 << 1,
privateMethods: 1 << 2,
privateIn: 1 << 3,
staticBlocks: 1 << 4,
}
: {
//classes: 1 << 0,
fields: 1 << 1,
privateMethods: 1 << 2,
decorators: 1 << 3,
privateIn: 1 << 4,
staticBlocks: 1 << 5,
},
);

const featuresSameLoose = new Map([
[FEATURES.fields, "@babel/plugin-proposal-class-properties"],
Expand Down
12 changes: 12 additions & 0 deletions packages/babel-helper-create-class-features-plugin/src/fields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,12 @@ const privateNameHandlerSpec: Handler<PrivateNameState & Receiver> & Receiver =

return optimiseCall(this.get(member), this.receiver(member), args, true);
},

delete() {
throw new Error(
"Internal Babel error: deleting private elements is a parsing error.",
);
},
};

const privateNameHandlerLoose: Handler<PrivateNameState> = {
Expand Down Expand Up @@ -514,6 +520,12 @@ const privateNameHandlerLoose: Handler<PrivateNameState> = {
optionalCall(member, args) {
return t.optionalCallExpression(this.get(member), args, true);
},

delete() {
throw new Error(
"Internal Babel error: deleting private elements is a parsing error.",
);
},
};

export function transformPrivateNamesUsage(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@ import type * as t from "@babel/types";
export function assertFieldTransformed(
path: NodePath<t.ClassProperty | t.ClassDeclaration>,
) {
// TODO(Babel 8): Also check path.node.definite

if (path.node.declare) {
if (
path.node.declare ||
(process.env.BABEL_8_BREAKING
? path.isClassProperty({ definite: true })
: false)
) {
throw path.buildCodeFrameError(
`TypeScript 'declare' fields must first be transformed by ` +
`@babel/plugin-transform-typescript.\n` +
Expand Down
Loading

0 comments on commit f3e9aee

Please sign in to comment.