Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize TS enums output #15467

Merged
merged 4 commits into from Mar 9, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
100 changes: 71 additions & 29 deletions packages/babel-plugin-transform-typescript/src/enum.ts
Expand Up @@ -6,6 +6,21 @@ type t = typeof t;

const ENUMS = new WeakMap<t.Identifier, PreviousEnumMembers>();

const buildEnumWrapper = template.statement(`
(function (ID) {
ASSIGNMENTS;
})(ID || (ID = {}));
`);

const buildPureEnumWrapper = template.expression(
`
/*#__PURE__*/(function (ID) {
ASSIGNMENTS;
})(ID || {})
`,
{ preserveComments: true },
);

export default function transpileEnum(
path: NodePath<t.TSEnumDeclaration>,
t: t,
Expand All @@ -18,24 +33,50 @@ export default function transpileEnum(
}

const name = node.id.name;
const { wrapper: fill, data } = enumFill(path, t, node.id);
const { fill, data, isPure } = enumFill(path, t, node.id);

switch (path.parent.type) {
case "BlockStatement":
case "ExportNamedDeclaration":
case "Program": {
path.insertAfter(fill);
if (seen(path.parentPath)) {
path.remove();
// todo: Consider exclude program with import/export
// && !path.parent.body.some(n => t.isImportDeclaration(n) || t.isExportDeclaration(n));
const isGlobal = t.isProgram(path.parent);

if (isPure && isGlobal) {
if (seen(path.parentPath)) {
path.replaceWith(
t.assignmentExpression(
"=",
t.cloneNode(node.id),
buildPureEnumWrapper(fill),
),
);
} else {
path.scope.registerDeclaration(
path.replaceWith(
t.variableDeclaration("var", [
t.variableDeclarator(node.id, buildPureEnumWrapper(fill)),
]),
)[0],
);
}
} else {
// todo: Consider exclude program with import/export
// && !path.parent.body.some(n => t.isImportDeclaration(n) || t.isExportDeclaration(n));
const isGlobal = t.isProgram(path.parent);
path.scope.registerDeclaration(
path.replaceWith(makeVar(node.id, t, isGlobal ? "var" : "let"))[0],
);
ENUMS.set(path.scope.getBindingIdentifier(name), data);
path.insertAfter(buildEnumWrapper(fill));

if (seen(path.parentPath)) {
path.remove();
} else {
path.scope.registerDeclaration(
path.replaceWith(
t.variableDeclaration(isGlobal ? "var" : "let", [
t.variableDeclarator(node.id),
]),
)[0],
);
}
}
ENUMS.set(path.scope.getBindingIdentifier(name), data);
break;
}

Expand All @@ -57,16 +98,6 @@ export default function transpileEnum(
}
}

function makeVar(id: t.Identifier, t: t, kind: "var" | "let" | "const") {
return t.variableDeclaration(kind, [t.variableDeclarator(id)]);
}

const buildEnumWrapper = template(`
(function (ID) {
ASSIGNMENTS;
})(ID || (ID = {}));
`);

const buildStringAssignment = template(`
ENUM["NAME"] = VALUE;
`);
Expand All @@ -83,7 +114,7 @@ const buildEnumMember = (isString: boolean, options: Record<string, unknown>) =>
* `(function (E) { ... assignments ... })(E || (E = {}));`
*/
function enumFill(path: NodePath<t.TSEnumDeclaration>, t: t, id: t.Identifier) {
const { enumValues: x, data } = translateEnumValues(path, t);
const { enumValues: x, data, isPure } = translateEnumValues(path, t);
const assignments = x.map(([memberName, memberValue]) =>
buildEnumMember(t.isStringLiteral(memberValue), {
ENUM: t.cloneNode(id),
Expand All @@ -93,11 +124,12 @@ function enumFill(path: NodePath<t.TSEnumDeclaration>, t: t, id: t.Identifier) {
);

return {
wrapper: buildEnumWrapper({
fill: {
ID: t.cloneNode(id),
ASSIGNMENTS: assignments,
}),
data: data,
},
data,
isPure,
};
}

Expand Down Expand Up @@ -141,10 +173,11 @@ export function translateEnumValues(path: NodePath<t.TSEnumDeclaration>, t: t) {
// Start at -1 so the first enum member is its increment, 0.
let constValue: number | string | undefined = -1;
let lastName: string;
let isPure = true;

return {
data: seen,
enumValues: path.get("members").map(memberPath => {
const enumValues: Array<[name: string, value: t.Expression]> = path
.get("members")
.map(memberPath => {
const member = memberPath.node;
const name = t.isIdentifier(member.id) ? member.id.name : member.id.value;
const initializerPath = memberPath.get("initializer");
Expand All @@ -161,6 +194,10 @@ export function translateEnumValues(path: NodePath<t.TSEnumDeclaration>, t: t) {
value = t.stringLiteral(constValue);
}
} else {
if (isPure && !initializerPath.isPure()) {
isPure = false;
}
nicolo-ribaudo marked this conversation as resolved.
Show resolved Hide resolved

if (initializerPath.isReferencedIdentifier()) {
ReferencedIdentifier(initializerPath, {
t,
Expand Down Expand Up @@ -197,7 +234,12 @@ export function translateEnumValues(path: NodePath<t.TSEnumDeclaration>, t: t) {

lastName = name;
return [name, value];
}) as Array<[name: string, value: t.Expression]>,
});

return {
isPure,
data: seen,
enumValues,
};
}

Expand Down
@@ -1,5 +1,4 @@
// Not type-correct code
var E;
(function (E) {
var E = /*#__PURE__*/function (E) {
E[E["A"] = true] = "A";
})(E || (E = {}));
}(E || {});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This IIFE always returns undefined, it should return the enum.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops! Thanks!

@@ -1,3 +1,2 @@
// With --isolatedModules, TSC ignores the "const" modifier when compiling enums
var E;
(function (E) {})(E || (E = {}));
var E = /*#__PURE__*/function (E) {}(E || {});
@@ -1,5 +1,4 @@
var E;
(function (E) {
var E = /*#__PURE__*/function (E) {
E[E["a"] = 0] = "a";
E[E["b"] = 3] = "b";
E[E["c"] = 1] = "c";
Expand All @@ -18,4 +17,4 @@ var E;
E[E["p"] = -3] = "p";
E[E["q"] = 5.4] = "q";
E[E["r"] = 6.4] = "r";
})(E || (E = {}));
}(E || {});
@@ -1,5 +1,4 @@
var E;
(function (E) {
var E = /*#__PURE__*/function (E) {
E[E["x"] = 0] = "x";
E[E["y"] = 1] = "y";
})(E || (E = {}));
}(E || {});
@@ -1,5 +1,4 @@
var E;
(function (E) {
var E = /*#__PURE__*/function (E) {
E[E["a"] = 10] = "a";
E[E["b"] = 10] = "b";
})(E || (E = {}));
}(E || {});
@@ -1,10 +1,9 @@
var x = 10;
var Foo;
(function (Foo) {
var Foo = /*#__PURE__*/function (Foo) {
Foo[Foo["a"] = 10] = "a";
Foo[Foo["b"] = 10] = "b";
Foo[Foo["c"] = 20] = "c";
})(Foo || (Foo = {}));
}(Foo || {});
var Bar;
(function (Bar) {
Bar[Bar["D"] = 10] = "D";
Expand Down
@@ -1,8 +1,7 @@
var E;
(function (E) {
var E = /*#__PURE__*/function (E) {
E[E["x"] = 1] = "x";
E[E["y"] = 2] = "y";
})(E || (E = {}));
(function (E) {
}(E || {});
E = /*#__PURE__*/function (E) {
E[E["z"] = 3] = "z";
})(E || (E = {}));
}(E || {});
@@ -1,14 +1,12 @@
var socketType;
(function (socketType) {
var socketType = /*#__PURE__*/function (socketType) {
socketType[socketType["SOCKET"] = 0] = "SOCKET";
socketType[socketType["SERVER"] = 1] = "SERVER";
socketType[socketType["IPC"] = 2] = "IPC";
})(socketType || (socketType = {}));
var constants;
(function (constants) {
}(socketType || {});
var constants = /*#__PURE__*/function (constants) {
constants[constants["SOCKET"] = 0] = "SOCKET";
constants[constants["SERVER"] = 1] = "SERVER";
constants[constants["IPC"] = 2] = "IPC";
constants[constants["UV_READABLE"] = 3] = "UV_READABLE";
constants[constants["UV_WRITABLE"] = 4] = "UV_WRITABLE";
})(constants || (constants = {}));
}(constants || {});
@@ -1,4 +1,3 @@
var E;
(function (E) {
var E = /*#__PURE__*/function (E) {
E["A"] = "Hey";
})(E || (E = {}));
}(E || {});
@@ -1,7 +1,6 @@
var E;
(function (E) {
var E = /*#__PURE__*/function (E) {
E[E["A"] = 0] = "A";
E["B"] = "";
E[E["A2"] = 0] = "A2";
E["B2"] = "";
})(E || (E = {}));
}(E || {});
@@ -1,4 +1,3 @@
var E;
(function (E) {
var E = /*#__PURE__*/function (E) {
E["A"] = "HALLOWERLD";
})(E || (E = {}));
}(E || {});
Expand Up @@ -2,18 +2,16 @@

const BaseValue = 10;
const Prefix = "/data";
var Values; // 12
(function (Values) {
var Values = /*#__PURE__*/function (Values) {
Values[Values["First"] = 10] = "First";
Values[Values["Second"] = 11] = "Second";
Values[Values["Third"] = 12] = "Third";
})(Values || (Values = {}));
}(Values || {}); // 12
const xxx = 100 + Values.First;
const yyy = xxx;
var Routes;
(function (Routes) {
var Routes = /*#__PURE__*/function (Routes) {
Routes["Parts"] = "/data/parts";
Routes["Invoices"] = "/data/invoices";
Routes["x"] = "10/x";
Routes["y"] = "110/y";
})(Routes || (Routes = {}));
}(Routes || {});
@@ -1,19 +1,17 @@
export class C2 {}
// everything removed
export { C2 as C3 }; // only C2->C3
var BB;
(function (BB) {
var BB = /*#__PURE__*/function (BB) {
BB[BB["K"] = 0] = "K";
})(BB || (BB = {}));
(function (BB) {
}(BB || {});
BB = /*#__PURE__*/function (BB) {
BB["L"] = "LL";
})(BB || (BB = {}));
}(BB || {});
export { BB }; // only BB

// everything removed
export { BB as BB1 }; // BB->BB1
var BB2;
(function (BB2) {})(BB2 || (BB2 = {}));
var BB2 = /*#__PURE__*/function (BB2) {}(BB2 || {});
function foo() {}
export { BB2 as BB3, foo }; // only BB2->BB3 and foo

Expand Down
@@ -1,4 +1,3 @@
var None;
(function (None) {})(None || (None = {}));
var None = /*#__PURE__*/function (None) {}(None || {});
;
export {};
@@ -1,6 +1,5 @@
var Enum;
(function (Enum) {
var Enum = /*#__PURE__*/function (Enum) {
Enum[Enum["A"] = 0] = "A";
})(Enum || (Enum = {}));
}(Enum || {});
;
export {};
@@ -1,6 +1,5 @@
import { A } from 'lib';
var Enum;
(function (Enum) {
var Enum = /*#__PURE__*/function (Enum) {
Enum[Enum["id"] = A] = "id";
})(Enum || (Enum = {}));
}(Enum || {});
;
@@ -1,7 +1,6 @@
var A;
(function (A) {
var A = /*#__PURE__*/function (A) {
A[A["C"] = 2] = "C";
})(A || (A = {}));
}(A || {});
(function (_A) {
const B = _A.B = 1;
})(A || (A = {}));
@@ -1,5 +1,4 @@
var Example;
(function (Example) {
var Example = /*#__PURE__*/function (Example) {
Example[Example["Value"] = 0] = "Value";
})(Example || (Example = {}));
}(Example || {});
foo;
@@ -1,5 +1,4 @@
var Example;
(function (Example) {
var Example = /*#__PURE__*/function (Example) {
Example[Example["Value"] = 0] = "Value";
})(Example || (Example = {}));
}(Example || {});
foo;